Skip to content

perf(console): keep the lazily-imported @objectstack/lint out of the eager chunk - #5323

Merged
os-support-ai merged 2 commits into
mainfrom
claude/issue-5266-vendor-objectstack-chunk-lookahead
Aug 19, 2026
Merged

perf(console): keep the lazily-imported @objectstack/lint out of the eager chunk#5323
os-support-ai merged 2 commits into
mainfrom
claude/issue-5266-vendor-objectstack-chunk-lookahead

Conversation

@os-support-ai

@os-support-aios-support-ai commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Fixes#5266

The vendor-objectstackadvancedChunks group matched every @objectstack/* package, so @objectstack/lint was folded in beside @objectstack/spec and @objectstack/client. Those two are reached synchronously from the app entry, so the group's chunk is a static import of index-*.js — and the group overrides the linter's async-only reachability. Every console page load downloaded and parsed the whole linter.

The linter has exactly one runtime reference in this repo: packages/app-shell/src/preview/capabilityLint.ts, behind a deliberate await import('@objectstack/lint') that runs only when an author publishes in the metadata designer.

Two commits: the config fix, and the pin in scripts/__tests__/ that mirrors the config constant.

Premise re-derived on current main before implementing

The card measured at pin 82a94170c405 / framework 6f40ed736; main has taken 120 commits since. Re-checked:

  • pnpm-lock.yaml diff over that range: 1,138 lines changed, 0 of them mentioning @objectstack (counter-probe: 134 resolution: lines in the same diff, so the zero is real, not an empty diff). The dependency pin has not moved.
  • @objectstack/lint@17.0.0dist/index.js is 532,262 bytes — byte-identical to the card's figure.
  • VENDOR_OBJECTSTACK_TEST is unchanged between the pin and current main; the only vite.config.ts commit in between (578e02516) added an unrelated @object-ui/types/zod alias.

One card figure did move: the eager closure is 58 chunks on main, not 59. The parity check below is therefore 58/58. Everything else reproduced.

Both lookahead alternatives are load-bearing

Under pnpm the linter resolves through the store, so its module id — read out of the build's own module map, not assumed — is:

.../node_modules/.pnpm/@objectstack+lint@17.0.0_.../node_modules/@objectstack/lint/dist/index.js

That single path contains both/@objectstack+ and /node_modules/@objectstack/. Tested against the real path rather than picked by inspection:

variantlintspecclienta hypothetical lint-utils sibling
baseline (no lookahead)matchesmatchesmatchesmatches
lookahead on alternative A onlymatchesmatchesmatchesmatches
lookahead on alternative B onlymatchesmatchesmatchesmatches
both (this PR)no matchmatchesmatchesmatches

Guarding one alternative alone leaves the other matching and looks like a fix while changing nothing. The lookaheads are tight (lint[\\/], lint@) so a future @objectstack/lint-* sibling still groups here rather than scattering into its importers' chunks.

Measured — baseline beside result

Eager closure = every chunk reachable from index.html through static imports only, walked with es-module-lexer; gzip read from the build's own emitted .gz. Both builds at eb503920f's tree, vite build, compression + visualizer active.

baselinethis PRdelta
eager closure, gzipped3,957,3013,866,123−91,178 (−89.0 KiB)
eager closure, raw13,626,58313,330,253−296,330
eager chunk count58580
emitted JS chunks506507+1

The eager chunk name sets are identical — nothing entered or left the closure; only the linter's bytes did. The +1 emitted chunk is the linter's new home, assets/dist-*.js (294,864 raw / 91,271 gzipped), which is not in the eager closure; it is fetched only when an author publishes in the metadata designer, which is what the await import was always asking for. vendor-objectstack itself drops 5,352,089 to 5,055,720 raw.

The linter's placement is verified directly, not inferred from the delta

Two independent reads, each with a counter-probe:

  • Path-based (visualizer module map): @objectstack/lint/dist/index.js moves from assets/vendor-objectstack-BM4dkOHC.js (eager) to assets/dist-LXdey3OB.js (not eager). Counter-probe: @objectstack/spec is still in vendor-objectstack, so the exclusion is not over-broad.
  • Content-based: three lint rule-id markers (widget-legacy-analytics-unrenderable, visibility-root-mislayered, view-ref-form-target-missing) hit 0 eager chunks after the change, all three in assets/dist-LXdey3OB.js. Counter-probe: the substring objectstack still hits 27 chunks (19 eager), so the scan is live and the zero is a real zero.

Regression checks

1. A build-output assertion, stated plainly

This part is not a unit test. The invariant is a property of the emitted chunk graph, so it is asserted where it is decided: a new assert-lazy-linter-stays-lazy plugin in apps/console/vite.config.ts walks the bundle at generateBundle, computes the eager closure from chunk.imports (never dynamicImports), and fails the build if any eager chunk holds an @objectstack/lint module. It follows viteMaplibreWorker's existing precedent in this file: an invisible bundling edge must fail the BUILD, loudly.

It is two-part on purpose. Asserting "no eager chunk holds the linter" alone would also pass if the walk found nothing at all — a green check with no subject. A counter-probe runs first and demands a known-eager @objectstack/spec, so the linter verdict is only ever read after the walk has proven it can see the very chunk the linter used to hide in.

This costs no new CI job: the console is already built by ci.yml (Build & E2E), live-e2e.yml, and performance-budget.yml. Worth noting why the existing bundle budget never caught this — it weighs the index-*.js entry chunk alone against 350 KB, and these bytes were sitting in a vendor chunk. Filed separately as #5324.

2. The existing pin in scripts/__tests__/, re-pinned and strengthened

scripts/__tests__/vite-objectstack-spec-dist.test.ts mirrors the config's VENDOR_OBJECTSTACK_TEST as a literal BASE_VENDOR_TEST and asserts on it twice. The lookahead turned both red. Neither assertion was relaxed — I worked out what each was protecting first, and both hold verbatim once the mirror is current:

  • .source equality lives in "leaves all four flagged surfaces at their baseline values". Its intent is "an unset OBJECTSTACK_SPEC_DIST leaves the group test inert", not "the regex is this string". Kept as exact equality.
  • startsWith lives in the override-set case. Its intent is "the derivation WIDENS the baseline (appends an arm) rather than replacing it"resolveSpecDistInjection builds `${base.source}|${dir}${SEP}`. My change does not touch the derivation, so this holds unchanged. Kept as-is.

Strengthened, because string equality alone would let a future edit keep the shape while changing membership. Added, reading the live config's regex rather than the mirror: the real pnpm lint id must not match; spec (both spellings) and client must match, so that rejection cannot pass by matching nothing; @objectstack/lint-utilsmust match, pinning the exclusion to the lint package rather than a lint* prefix. The same exclusion is also asserted on the widened regex in both the live-config case and the resolveSpecDistInjection unit — otherwise a spec-dist build could silently re-eagerize the linter while a released build stayed lazy.

Reverse-verification — predicted before running, then observed

Ablation A — the guard. Reverted the regex only, kept the build assertion. Predicted: non-zero exit at generateBundle; the second assertion fires naming the vendor-objectstack chunk; the counter-probe does not fire (spec is eager either way); plain red. Observed: all four. BUILD_EXIT=1, stack in PluginContextImpl.generateBundle, counter-probe failed count 0 / is in the EAGER closure count 1, error naming assets/vendor-objectstack-BM4dkOHC.js — the baseline chunk hash exactly, so reverting reproduces the original bundle rather than some third state.

Ablation B — the pin catches a config revert. Reverted vite.config.ts only, kept the updated pin. Predicted: red in exactly the 2 cases that read the live config; the helper-level cases stay green because they feed BASE_VENDOR_TEST into resolveSpecDistInjection directly and never read the config. Observed: exactly that — Tests 2 failed | 16 passed (18).

Ablation C — the strengthening is load-bearing. Ablation B fails on .source first, so it does not by itself exercise the new semantic assertions. Reverted the config and the mirror in lockstep, so string equality and startsWith both pass and only semantics can catch it. Observed:3 failed / 15 passed — one more than Ablation B, all expected true to be false, and it reaches the helper-level case that a config-only revert cannot. Direction here is "diagnostics increase", not the plain red of A and B.

The fix was committed before each ablation; restoration was proven byte-identical by sha256sum -c, tree clean.

Not taken: the documented trap

Switching the import to @objectstack/lint/runtime is not a fix and was not attempted — that subpath reaches 70 of the 72 modules the main entry reaches, is 93.6% of its size, and does not export validateCapabilityReferences at all. Recorded in objectstack#9772; the reasoning is now also inline in the guard's failure message, where the next person reaching for it will actually read it.

Verification run at 6db2fa648

All from the repository root unless noted:

  • pnpm exec vitest run scripts/__tests__/vite-objectstack-spec-dist.test.ts18/18 passed.
  • pnpm exec vitest run scripts/__tests__/57 files, 1,291 tests, all passed. Widened deliberately, to check whether any other pin in that directory encodes the same pattern. None does: a repo-wide grep for the pattern finds exactly two sites, this test and the config (counter-probed — the same grep finds both known sites).
  • pnpm exec vitest run apps/console/ --maxWorkers=255 files, 658 tests, all passed (vitest root echoed as the repo root, so not the package-cwd phantom run).
  • tsc --noEmit (console app program) — 0, after pnpm --filter '@object-ui/console^...' build. The first run's 20 TS2307/TS2882 were unbuilt @object-ui/* dists, none involving this change.
  • tsc -b tsconfig.node.json --force0. That is the program that actually contains vite.config.ts; the app program does not (include: ["src", "dev"]).
  • eslint on both changed files — 0 errors. 3 pre-existing no-explicit-any warnings in untouched regions of the test file; this diff adds none.
  • check:control-bytes (4,714 files), check:phantom-deps, check:self-import, check:esm-specifiers — all 0.
  • vite build with the guard active — exit 0, byte-for-byte the same output as the pre-guard fixed build.

Changeset: none owed — node scripts/check-changeset-presence.mjs reports 0 of them under the src/ of a package the release covers. No label applied (this repo has no skip-changeset mechanism).

Scope check: in-flight #5254's file surface is packages/components/src/renderers/form/** — no overlap.


Generated by Claude Code

…eager chunk
The `vendor-objectstack` advancedChunks group matched every `@objectstack/*`
package, so `@objectstack/lint` was folded in beside `@objectstack/spec` and
`@objectstack/client`. Those two are reached synchronously from the app entry,
so the group's chunk is a static import of `index-*.js` — and the group
overrides the linter's async-only reachability. Every console page load
downloaded and parsed the whole linter.
The linter has exactly one runtime reference in this repo:
`packages/app-shell/src/preview/capabilityLint.ts`, behind a deliberate
`await import('@objectstack/lint')` that runs only when an author publishes in
the metadata designer.
Exclude it from the group with a negative lookahead on BOTH alternatives —
under pnpm the module id is
`.../.pnpm/@objectstack+lint@<v>/node_modules/@objectstack/lint/dist/index.js`,
which matches both, so guarding one alone changes nothing.
Also add a build-time assertion (`assert-lazy-linter-stays-lazy`) that fails
the build if the linter ever re-enters the eager closure, with a counter-probe
on `@objectstack/spec` so the check cannot pass by finding nothing.
Measured on 3fbbea1 (eager closure = chunks statically reachable from
index.html):
eager gzip 3,957,301 -> 3,866,123 (-91,178 = -89.0 KiB)
eager raw 13,626,583 -> 13,330,253 (-296,330)
eager chunk count 58 -> 58 (identical chunk set)
Part of #5266
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)25.3 KB350 KB
Entry fileindex-CcmX4dUD.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)9.83KB3.70KB
app-shell (runtime-config.js)7.42KB2.32KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)8.92KB3.41KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)29.33KB7.05KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.13KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.64KB2.21KB
auth (SocialSignInButtons.js)9.60KB3.89KB
auth (UserMenu.js)3.40KB1.22KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.79KB
auth (createAuthenticatedFetch.js)6.34KB2.43KB
auth (index.js)2.71KB1.22KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.02KB0.88KB
auth (useIsWorkspaceAdmin.js)1.61KB0.85KB
collaboration (CommentThread.js)26.07KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.65KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)506.81KB113.40KB
core (index.js)4.11KB1.62KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)159.80KB44.34KB
fields (index.js)237.07KB59.46KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.42KB1.39KB
i18n (pickLocalized.js)3.69KB1.73KB
i18n (provider.js)23.13KB7.63KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)29.43KB7.15KB
i18n (useSafeTranslation.js)7.77KB3.13KB
layout (index.js)39.16KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.74KB
mobile (index.js)1.50KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.71KB0.42KB
mobile (useResponsiveConfig.js)1.36KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)9.35KB3.31KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.42KB1.42KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.91KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.52KB
permissions (usePermissions.js)1.81KB0.83KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.62KB12.83KB
plugin-charts (index.js)64.75KB18.37KB
plugin-chatbot (index.js)181.21KB43.14KB
plugin-dashboard (index.js)127.92KB32.80KB
plugin-designer (index.js)212.39KB42.83KB
plugin-detail (index.js)241.46KB60.56KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)124.19KB30.20KB
plugin-gantt (index.js)164.10KB39.87KB
plugin-grid (index.js)198.22KB53.28KB
plugin-kanban (index.js)52.93KB14.60KB
plugin-list (index.js)111.66KB27.13KB
plugin-map (index.js)20.08KB6.62KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.49KB11.93KB
plugin-timeline (index.js)26.68KB7.66KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.55KB20.67KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.44KB0.22KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)36.10KB12.26KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.33KB0.69KB
react (schema-input.js)1.45KB0.83KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (index.js)4.77KB2.16KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)10.76KB3.17KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)6.92KB2.40KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-retry.js)4.32KB2.02KB
types (index.js)3.08KB1.53KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)0.20KB0.18KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

… it MEANS
`scripts/__tests__/vite-objectstack-spec-dist.test.ts` mirrors the console
config's `VENDOR_OBJECTSTACK_TEST` as a literal and asserts on it twice, so the
lookahead added in the previous commit turned it red. The mirror is updated to
the new spelling; neither assertion is relaxed.
Both keep their original intent, which is not "the regex is this string":
- `.source` equality is inside "leaves all four flagged surfaces at their
baseline values" — it pins that an unset OBJECTSTACK_SPEC_DIST leaves the
group test INERT.
- `startsWith` is inside the override-set case — it pins that the derivation
WIDENS the baseline (appends an arm) rather than replacing it.
Both still hold verbatim once the mirror is current, so both are kept as-is.
Strengthened rather than merely re-pasted — string equality alone would let a
future edit keep the shape while changing which modules the group catches:
- the live config's regex must reject a real pnpm `@objectstack/lint` id;
- counter-probed with spec (both spellings) and client, which must still
match, so the rejection above cannot pass by matching nothing at all;
- `@objectstack/lint-utils` must still match, pinning that the exclusion is
scoped to the `lint` package and not a `lint*` prefix;
- the WIDENED regex must keep the exclusion too, in both the live-config case
and the `resolveSpecDistInjection` unit — otherwise a spec-dist build could
silently re-eagerize the linter while a released build stayed lazy.
Part of #5266
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)25.3 KB350 KB
Entry fileindex-BKmlQoeI.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)9.83KB3.70KB
app-shell (runtime-config.js)7.42KB2.32KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)8.92KB3.41KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)29.33KB7.05KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.13KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.64KB2.21KB
auth (SocialSignInButtons.js)9.60KB3.89KB
auth (UserMenu.js)3.40KB1.22KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.79KB
auth (createAuthenticatedFetch.js)6.34KB2.43KB
auth (index.js)2.71KB1.22KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.02KB0.88KB
auth (useIsWorkspaceAdmin.js)1.61KB0.85KB
collaboration (CommentThread.js)26.07KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.65KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)506.75KB113.40KB
core (index.js)4.11KB1.62KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)159.80KB44.34KB
fields (index.js)237.07KB59.46KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.42KB1.39KB
i18n (pickLocalized.js)3.69KB1.73KB
i18n (provider.js)23.13KB7.63KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)29.43KB7.15KB
i18n (useSafeTranslation.js)7.77KB3.13KB
layout (index.js)39.16KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.74KB
mobile (index.js)1.50KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.71KB0.42KB
mobile (useResponsiveConfig.js)1.36KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)9.35KB3.31KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.42KB1.42KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.91KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.52KB
permissions (usePermissions.js)1.81KB0.83KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.62KB12.83KB
plugin-charts (index.js)64.75KB18.37KB
plugin-chatbot (index.js)181.21KB43.14KB
plugin-dashboard (index.js)127.92KB32.80KB
plugin-designer (index.js)212.39KB42.83KB
plugin-detail (index.js)241.46KB60.56KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)124.19KB30.20KB
plugin-gantt (index.js)164.10KB39.87KB
plugin-grid (index.js)198.22KB53.28KB
plugin-kanban (index.js)52.93KB14.60KB
plugin-list (index.js)111.66KB27.13KB
plugin-map (index.js)20.08KB6.62KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.49KB11.93KB
plugin-timeline (index.js)26.68KB7.66KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.55KB20.67KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.44KB0.22KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)36.10KB12.26KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.33KB0.69KB
react (schema-input.js)1.45KB0.83KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (index.js)4.77KB2.16KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)10.76KB3.17KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)6.92KB2.40KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-retry.js)4.32KB2.02KB
types (index.js)3.08KB1.53KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)0.20KB0.18KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-support-ai
os-support-ai marked this pull request as ready for review August 19, 2026 15:30
@os-support-aiClaude

Copy link
Copy Markdown
CollaboratorAuthor

ACCEPT — PM review, round 17.

  • Path surface (git diff --name-only origin/main... @ 6db2fa6): 2 files — apps/console/vite.config.ts, scripts/__tests__/vite-objectstack-spec-dist.test.ts. Zero governed-surface hits.Changeset Declaration green, so the presence gate is satisfied for this surface.
  • Gates: every gate job completed: success — Lint, Type Check, Test shards 1–4, Build & E2E, Build Docs, Doc Snippet / Doc Component Type Check, Changeset Declaration / Bump Policy / Fixed Group, Bundle Analysis, Control Byte Scan, Internal Docs Link Check, Skill Guide Path Check. No cancelled, no in_progress.

The part that mattered: the pin was strengthened, not relaxed

The shard failure was a real in-scope failure — scripts/__tests__/vite-objectstack-spec-dist.test.ts held the pre-fix regex literal — and the lazy fix was available: drop the equality, match loosely, or delete :351. None of that happened. Both original assertions survive intact (.source equality andstartsWith), and the update adds semantics the old pin never had:

  • Read off the live config, not the mirror: vendor!.test.test(LINT_ID) is false — so a future rewrite that keeps the regex shape while changing its membership fails, which a pure string-equality pin would have waved through.
  • Counter-probed, and this is the line that makes the rest trustworthy: @objectstack/spec (both pnpm spellings) and @objectstack/client must still match. Without it, "lint is excluded" would also pass if the group test had been broken into matching nothing at all.
  • Scope, measured: @objectstack/lint-utilsstill matches — the lookahead is package-scoped, not a lint* prefix. That is the failure mode a hand-written lookahead actually has.
  • Widening: the spec-dist injection must not resurrect lint, or a spec-dist build would silently re-eagerize the linter while released builds stayed lazy.

Checked against the standard I set in the send-back — "if the updated pin passes against the old regex too, you have relaxed it into uselessness": it does not. The .source equality fails against the old literal, and the semantic LINT_ID assertion fails against it too, because the old regex matched lint. Strictly stronger in both directions.

The payload itself stands as measured: −91,178 bytes gzipped (−89.0 KiB) off the eager console chunk, plus a build-time assert-lazy-linter-stays-lazy so the laziness cannot regress silently.

Merging via the queue.


Generated by Claude Code

@os-support-ai
os-support-ai added this pull request to the merge queueAug 19, 2026
Merged via the queue into main with commit 6d5368aAug 19, 2026
21 checks passed
@os-support-ai
os-support-ai deleted the claude/issue-5266-vendor-objectstack-chunk-lookahead branch August 19, 2026 15:31
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants

@os-support-ai@claude