Skip to content

Commit 823d0f6

Browse files
fix(console): count only objectui's own packages in the chunk-membership report, so the eager-closure gate stops reading @objectstack/core as objectui core (#26225)
<!-- claude-projects-attribution: {"github_login":"os-steve"} --> _Requested by **Steve** · [project thread](https://claude.ai/code/project/chan_01FdL1XeUyvVNxLreh3F4Qi9?thread=cmsg_01FdL1XeUyvVNxLreh3F4Qi9CsYCNfbV9QvfmNCbWe36wP)_ Part of #26131 Clause-②: no Metadata-protocol: none ## The module, named The stray module is not an objectui module. It is `packages/core/dist/logger.js` at the objectstack monorepo ROOT, a file of the framework package `@objectstack/core` (reached through `@objectstack/client`, which depends on it). It lands in `vendor-objectstack`, which is the right chunk for it: the vendor group's workspace alternative claims every module under the root `packages/` directory. The gate counted it as objectui's `ui/packages/core` because of how `emitChunkMembershipReport` in `ui/apps/console/vite.config.ts` attributes a module id. It matched any `/packages/NAME/` segment anywhere in the id. Inside the monorepo the framework's workspace packages resolve to a second `packages/` directory, and four of its package names are objectui package names too: `cli`, `core`, `sdui-parser` and `types`. So `@objectstack/core`'s logger became "1 of objectui core's 90 modules", in `vendor-objectstack`. How it was named: a throwaway Vite plugin, added through a wrapper config and deleted afterwards, listed every emitted module id under a root `packages/` directory whose name collides with an objectui package. On `main` that is exactly two ids: `packages/core/dist/logger.js` in `vendor-objectstack`, and `packages/sdui-parser/dist/index.mjs` in a lazy chunk named `dist`. In `chunk-membership.json` on `main`, the only `core` directory counted in `vendor-objectstack` is `dist`. objectui's `packages/core` is aliased to `src` and has no `dist` module in the bundle. ## Why neither suggested route - (a) A grouping change would move a framework module out of the vendor chunk it belongs in, and the only thing it would serve is a miscount. - (b) Changing the declaration does not help. `framework` really does hold all 89 of objectui core's modules. Dropping `core` from the list would leave the package unwatched. The defect is in what the report counts, so this PR fixes the producer, the membership emitter in the same `vite.config.ts` file the claim names. Grouping rules, ceilings, baselines and the declaration are untouched. ## Change - `ui/scripts/vite-objectstack-workspace.ts`: `objectuiPackagesDir(consoleDir)` and `workspaceModuleMatcher(packagesDir)`. The matcher is anchored on objectui's own `packages/` directory and escapes the path. It lives next to `workspaceChunkTests` because it fixes the same monorepo-layout effect, and because a config file's constants cannot be imported by a test. - `ui/apps/console/vite.config.ts`: `emitChunkMembershipReport` takes its `WORKSPACE_MODULE` from that matcher, and the docblock says what the population is. - `ui/scripts/__tests__/vite-objectstack-workspace.test.ts`: five cases. They cover attribution of an objectui module (package and rest of the id), refusal of each of the four colliding framework names, anchoring, escaping, and that the config uses the matcher. - `.changeset/26131-console-chunk-membership-attribution.md`: `patch` on `@object-ui/console`, because `dist/chunk-membership.json` ships in the package's `files`. ## Readings, before and after All on this container: the `@object-ui/console^...` closure built with turbo (70/70), then `CI=true pnpm exec vite build` in `ui/apps/console`, then `node scripts/check-eager-closure-budget.mjs` in `ui/`. "Before" is base `7111e14a`'s `vite.config.ts`, built from the same tree through a temporary `--config`. "After" is this branch at `8831aac4`. | reading | before (`main` config) | after (this branch) | |---|---|---| | gate exit | 1 | 0 | | membership half | red: `packages/core` 89 in `framework`, 1 in `vendor-objectstack` | green: `packages/core` 89 in `framework`; react 51; types 16 + `src/zod` 10 in `types-zod`; components 217; fields 90 + date-picker 2 | | eager closure | 1937.7 KB gz, 88 of 2390 chunks | 1937.7 KB gz, 88 of 2390 chunks | | emitted `*.js` files (sha256 of all 2395) | — | identical to before | | `eager-closure.json` | — | byte-identical to before | `chunk-membership.json`, compared by package: 32 of the 38 package keys are unchanged. The ones that changed are exactly the framework modules that drop out: `spec` (73 modules across 6 chunks), `client`, `formula`, `lint`, the logger counted under `core`, and the root `sdui-parser` counted under objectui's `sdui-parser`. Every objectui package count is unchanged. After merging `main` at `e6dfeb08`, the head is `f1393a3c`. On that head the closure and Console were rebuilt and the gate re-run: exit 0, membership green with the same counts, eager closure 1938.7 KB gz across 88 of 2390 chunks. The 185 cases in `vite-objectstack-workspace.test.ts` and `check-eager-closure-budget.test.ts` pass. The ablation was run once, with `node scripts/ablation-replace.mjs`. It put the unanchored segment regex back inside `workspaceModuleMatcher`. Two of the new cases went red: "does not attribute a framework package that shares an objectui package name" and the escaping case. The file was then restored: the blob hash matches HEAD and `git diff HEAD` is empty. ## Acceptance notes - The membership gate still runs in no workflow. That belongs to the ui CI port (epic #25677), as the card says. This PR adds no gate and does not touch `ui/.github/**` or the root `ci.yml`. - The `advancedChunks` group tests in `vite.config.ts` use the same unanchored segment form, `framework`'s among them (it matches `packages/core`, `packages/react` and `packages/types`). On this build that changes nothing: the probe found the only two colliding root modules in `vendor-objectstack` and in the lazy `dist` chunk, and none in `framework`. A group could still claim a framework module if the module ever stopped being `$initial`, because the vendor group would then no longer outrank it. Noted only: it is not reached today, and no card is filed for it. --- _Generated by [Claude Code](https://claude.ai/code/session_01UXdEMaH3vKYJxDUxiKdp4t)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 934810c commit 823d0f6

4 files changed

Lines changed: 93 additions & 4 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@object-ui/console': patch
3+
---
4+
5+
fix(console): the build's chunk-membership report attributes only objectui's own packages
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The Console build writes `dist/chunk-membership.json`, which the eager-closure gate reads to check that each budgeted chunk holds the packages it declares. Inside the objectstack monorepo the framework's own packages resolve to a second `packages/` directory, and four of their names are objectui package names too (`cli`, `core`, `sdui-parser`, `types`). The report matched any `packages/<name>/` segment, so `@objectstack/core`'s bundle file, which belongs in `vendor-objectstack`, was counted as a module of objectui's `packages/core`, and the gate reported `framework` as having lost one module to a vendor chunk. The report now counts only modules under objectui's own `packages/` directory. No chunk's contents change.

‎ui/apps/console/vite.config.ts‎

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,12 @@ import { viteCryptoStub } from '../../scripts/vite-crypto-stub.ts';
1919
import { viteMaplibreWorker } from '../../scripts/vite-maplibre-worker.ts';
2020
import { assertSingleZodInstance } from '../../scripts/vite-single-zod.ts';
2121
import { viteIneffectiveDynamicImports } from '../../scripts/vite-ineffective-dynamic-imports.ts';
22-
import { frameworkPackagesDir, workspaceChunkTests } from '../../scripts/vite-objectstack-workspace.ts';
22+
import {
23+
frameworkPackagesDir,
24+
objectuiPackagesDir,
25+
workspaceChunkTests,
26+
workspaceModuleMatcher,
27+
} from '../../scripts/vite-objectstack-workspace.ts';
2328
import { viteDeclaredLazyViews } from '../../scripts/vite-declared-lazy-views.ts';
2429
import { viteTypesZodLazy } from '../../scripts/vite-types-zod-lazy.ts';
2530
import { compression } from 'vite-plugin-compression2';
@@ -400,8 +405,9 @@ function emitEagerClosureReport(reportFileName = 'eager-closure.json'): Plugin {
400405
*
401406
* ## What it counts, said in words
402407
*
403-
* The population is every module in every emitted chunk whose id contains a
404-
* `packages/<name>/` segment — workspace source, not `node_modules`, and not
408+
* The population is every module in every emitted chunk whose id lies under
409+
* objectui's own `packages/<name>/` — workspace source, not `node_modules`, not
410+
* the objectstack framework's `packages/` beside it in the monorepo, and not
405411
* limited to the eager closure: a budgeted package's module hiding in a LAZY
406412
* chunk is exactly as much a membership breach as one hiding in an eager
407413
* neighbour, and the eager-closure walk cannot see it.
@@ -429,10 +435,15 @@ function emitChunkMembershipReport(reportFileName = 'chunk-membership.json'): Pl
429435
// The module ids rolldown records are realpaths, so a workspace module reads
430436
// as `<repo>/packages/<name>/src/...` no matter which symlink resolved it —
431437
// the same assumption the `advancedChunks` group tests above are written on.
438+
// Anchored on objectui's OWN `packages/` directory: inside the objectstack
439+
// monorepo the framework's packages resolve to a second `packages/`
440+
// directory whose names overlap objectui's (`core`, `types`, ...), and an
441+
// unanchored segment attributed `@objectstack/core` to objectui's
442+
// `packages/core`. `workspaceModuleMatcher` says why in full.
432443
// The second capture is the rest of the id — the package-relative path the
433444
// `directories` table is keyed by (its directory, `/`-separated, any query
434445
// suffix dropped: `?inline` and friends are the same file).
435-
const WORKSPACE_MODULE = /[\\/]packages[\\/]([^\\/]+)[\\/](.*)$/;
446+
const WORKSPACE_MODULE = workspaceModuleMatcher(objectuiPackagesDir(import.meta.dirname));
436447
const directoryOf = (rest: string) => {
437448
const file = rest.split('?')[0].replace(/\\/g, '/');
438449
const slash = file.lastIndexOf('/');

‎ui/scripts/__tests__/vite-objectstack-workspace.test.ts‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ import {
2626
candidateFrameworkPackagesDir,
2727
escapeRegExp,
2828
frameworkPackagesDir,
29+
objectuiPackagesDir,
2930
workspaceChunkTests,
31+
workspaceModuleMatcher,
3032
} from '../vite-objectstack-workspace.ts';
3133
import { isNestedWorkspace } from '../workspace-root.mjs';
3234

@@ -171,3 +173,39 @@ describe('workspaceChunkTests (objectstack ruling D1)', () => {
171173
expect(config).toContain('const LINT = WORKSPACE_CHUNK_TESTS.lint;');
172174
});
173175
});
176+
177+
describe('workspaceModuleMatcher (the chunk-membership attribution)', () => {
178+
const matcher = workspaceModuleMatcher(objectuiPackagesDir(path.join(MONOREPO, 'ui/apps/console')));
179+
180+
it("locates objectui's own packages dir two levels above the console", () => {
181+
expect(objectuiPackagesDir(path.join(MONOREPO, 'ui/apps/console'))).toBe(path.join(MONOREPO, 'ui/packages'));
182+
expect(objectuiPackagesDir(consoleDir)).toBe(path.join(repoRoot, 'packages'));
183+
});
184+
185+
it('attributes an objectui module to its package, with the package-relative rest', () => {
186+
const hit = matcher.exec(`${MONOREPO}/ui/packages/core/src/actions/ActionRunner.ts`);
187+
expect(hit?.[1]).toBe('core');
188+
expect(hit?.[2]).toBe('src/actions/ActionRunner.ts');
189+
expect(matcher.exec(`${MONOREPO}/ui/packages/types/src/zod/index.ts?inline`)?.[1]).toBe('types');
190+
});
191+
192+
it('does not attribute a framework package that shares an objectui package name', () => {
193+
// `@objectstack/core` resolves here in the monorepo; objectui has a `packages/core` too.
194+
for (const name of ['core', 'types', 'cli', 'sdui-parser']) {
195+
expect(matcher.test(`${MONOREPO}/packages/${name}/dist/index.js`)).toBe(false);
196+
}
197+
expect(matcher.test(`/elsewhere${MONOREPO}/ui/packages/core/src/index.ts`)).toBe(false);
198+
expect(matcher.test(`${MONOREPO}/ui/packages-extra/core/src/index.ts`)).toBe(false);
199+
});
200+
201+
it('escapes a directory with regex metacharacters so the prefix is matched literally', () => {
202+
const odd = workspaceModuleMatcher('/w/ui (v1.0)/packages');
203+
expect(odd.exec('/w/ui (v1.0)/packages/core/src/index.ts')?.[1]).toBe('core');
204+
expect(odd.test('/w/uiX(v1A0)/packages/core/src/index.ts')).toBe(false);
205+
});
206+
207+
it('is what the console config attributes membership with', () => {
208+
const config = readFileSync(path.join(consoleDir, 'vite.config.ts'), 'utf8');
209+
expect(config).toContain('const WORKSPACE_MODULE = workspaceModuleMatcher(objectuiPackagesDir(import.meta.dirname));');
210+
});
211+
});

‎ui/scripts/vite-objectstack-workspace.ts‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,33 @@ export function workspaceChunkTests(frameworkPackagesDir: string | null): Worksp
9494
lint: new RegExp('(@objectstack[\\\\/+]lint|^' + prefix + 'lint[\\\\/])'),
9595
};
9696
}
97+
98+
/**
99+
* objectui's own `packages/` directory, seen from a console directory
100+
* (`apps/console` → `../../packages`) — the same in both layouts. Pure.
101+
*/
102+
export function objectuiPackagesDir(consoleDir: string): string {
103+
return path.resolve(consoleDir, '../../packages');
104+
}
105+
106+
/**
107+
* The matcher `emitChunkMembershipReport` in `apps/console/vite.config.ts`
108+
* attributes a module id with: capture 1 is the objectui package name, capture
109+
* 2 the package-relative rest of the id.
110+
*
111+
* It is anchored on objectui's OWN `packages/` directory, and the monorepo is
112+
* why. Inside it the framework's packages are workspace links that Vite
113+
* resolves to `<monorepo root>/packages/<name>/...` (see the top of this file),
114+
* and four of those names are objectui package names too: `cli`, `core`,
115+
* `sdui-parser` and `types`. An unanchored `/packages/<name>/` segment reads
116+
* `@objectstack/core`'s `dist/index.js` — reached through `@objectstack/client`
117+
* and rightly grouped into `vendor-objectstack` — as a module of objectui's
118+
* `packages/core`, and the membership verdict then reports `framework`'s
119+
* declared package as having strayed into a vendor chunk. That verdict was
120+
* about attribution, not about where any objectui module landed. Standalone
121+
* the anchor changes nothing: no other `packages/` directory is in the graph.
122+
*/
123+
export function workspaceModuleMatcher(packagesDir: string): RegExp {
124+
const prefix = escapeRegExp(packagesDir.split(path.sep).join('/'));
125+
return new RegExp('^' + prefix + '[\\\\/]([^\\\\/]+)[\\\\/](.*)$');
126+
}

0 commit comments

Comments
 (0)