Skip to content

test(i18n-gate): pin the placeholder-spelling rule's two implementations to each other - #8028

Merged
baozhoutao merged 2 commits into
mainfrom
claude/issue-7310-placeholder-spelling-parity
Sep 6, 2026
Merged

test(i18n-gate): pin the placeholder-spelling rule's two implementations to each other#8028
baozhoutao merged 2 commits into
mainfrom
claude/issue-7310-placeholder-spelling-parity

Conversation

@claude

@claude claude Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Fixes #7310

objectui#3512's placeholder-spelling rule is implemented twice. This does not
merge the two — measured first, both merge directions are closed — it makes the
duplication mechanical instead of a promise: one corpus through both
implementations, red on any disagreement.

Step 1, as the card ordered: can packages/i18n be imported from scripts/?

No — and both resolvers say so, which is why both were asked. The gate is a
.mjs executed by node and its pin tests are type-checked by
tsconfig.scripts.json, so a shared module has to satisfy the type program AND
node's resolver.

A probe file under scripts/ importing @object-ui/i18n, no path mapping
added:

scripts/__probe-i18n-import.ts(2,32): error TS2307: Cannot find module
    '@object-ui/i18n' or its corresponding type declarations.        # tsc -p tsconfig.scripts.json, exit 2

RUNTIME_FAIL ERR_MODULE_NOT_FOUND: Cannot find package '@object-ui/i18n'
    imported from /home/user/objectui-issue-7310/[eval1]             # node, ESM

Two controls, so the reading is about the package and not about the method:

probe tsc -p tsconfig.scripts.json node
@object-ui/i18n exit 2, TS2307 ERR_MODULE_NOT_FOUND
@object-ui/test-support/defaults-table-scan (same file, same config) exit 0 OK
no probe file at all (baseline) exit 0 n/a

So moduleResolution: bundler is not the blocker. scripts/ is not a workspace
package, and node_modules/@object-ui/ at the root holds exactly one entry —
test-support, the one the root package.json declares. Reaching
@object-ui/i18n costs a new root dependency, and its exports map points at
./dist/*, which a fresh checkout has not built — putting a build step in front
of a gate that deliberately needs none ("the en key set is READ FROM AST, not
imported, so the gate needs no build").

This is the same answer PR #7309 recorded for @object-ui/core, and its comment
is the precedent for not answering it with a path mapping: "tsconfig.scripts.json
has no path mapping into the workspace packages, and adding one to type ONE line
would put every scripts/ file on a different module resolution than the one CI
type-checks them with."

Step 2: route (b) was tried, and it fails a gate

The card's next route is the shared module under scripts/, with the
packages/i18n test importing it. It was implemented, not reasoned about: the
rule moved to scripts/placeholder-spelling.mjs, the gate imported it and
check:i18n-keys produced byte-identical output, the i18n suite went green
at 1031 tests — and then:

$ pnpm --filter @object-ui/i18n type-check          # tsc --noEmit && tsc -p tsconfig.test.json
src/__tests__/fallback-placeholder-spelling-3512.test.ts(154,53): error TS7016:
    Could not find a declaration file for module '../../../../scripts/placeholder-spelling.mjs'.
src/__tests__/fallback-placeholder-spelling-3512.test.ts(282,12): error TS7006: Parameter 'why' implicitly has an 'any' type.
src/__tests__/fallback-placeholder-spelling-3512.test.ts(322,12): error TS7006: Parameter 'why' implicitly has an 'any' type.
Exit status 2

packages/i18n/tsconfig.test.json has no allowJs, and it is deliberately
fenced: paths: {} so workspace specifiers resolve through built declarations
rather than pulling source in, an explicit types list, include limited to
this package's own tests. The two ways out are a hand-written .d.mts beside
the .mjs — "a second source of truth, free to drift silently", rejected in
this repo on objectui#3494 — or widening that config to admit one CI script.
Widening the consumer to accept what the producer cannot supply is the shape
this repo's rules refuse, so route (b) was reverted rather than shipped.

Step 3: route (c), which the triage rated best value-for-cost

scripts/__tests__/placeholder-spelling-parity.test.ts feeds ONE corpus through
both implementations and fails naming the input they disagreed on and each
side's verdict.

The rule had to come out of the .test.ts first, and the reason is measured
rather than stylistic: a probe file declaring one test that imports
fallback-placeholder-spelling-3512.test.ts reported 19 passed — vitest
registers each describe into whichever file is collecting when the module
evaluates, so importing the suite drags its cases into the importer, and under
the unit project's isolate: false shared module graph which file they land in
is worker-order dependent. So placeholderViolations moved verbatim into
packages/i18n/src/__tests__/placeholder-spelling-rule.ts; the suite imports it
back, and every one of its cases is unchanged.

The corpus, and its floors

part size why
GRAMMAR_MATRIX 37 every dialect the rule names, the boundaries between them, both structurally out-of-range classes, the balance-check shapes, and both sides' own self-test cases verbatim
locale-pack string leaves 29,638 the real population surface 1 judges
discovered defaults rows 1,072 the real population surfaces 2/3 judge, via @object-ui/test-support's walk — the same one objectui#4401 uses
distinct inputs 23,420

Each part carries a floor (> 25000 leaves, > 700 rows, >= 37 matrix
entries, unreadable empty) so it cannot silently shrink to nothing while the
agreement assertion goes on passing — the objectui#3009 shape. A positive
control asserts at least 20 inputs actually reach a verdict and that all seven
of the rule's reasons appear among them, and one case asserts the two imported
functions are not the same object, which is the only way this gate could pass
while checking nothing.

The gate's own population (inline defaultValue text) is not walked here — that
would mean running the node gate's TypeScript pass inside a unit test. It is
reached transitively (check:i18n-keys reports 993 of 996 literal inline
defaults byte-equal to their en value, and every en value is in the corpus)
and the residue is recorded rather than claimed as covered.

Equivalence before, and after

Before either copy was touched, both were run over that corpus and the outputs
compared as JSON. After route (b) was reverted and route (c) landed, the same
corpus was replayed:

4403985ca72867b4a892ad0410166335  pre-gate.json     # unresolvableSpellings, on the merge base
4403985ca72867b4a892ad0410166335  pre-i18n.json     # placeholderViolations, on the merge base
4403985ca72867b4a892ad0410166335  post-shared.json  # after the change

23,420 inputs, one md5. 25 of them are flagged, all from the matrix — the real
corpus is clean today, which is what the two gates are for.

Reverse verification: predicted, then run, both directions

Predicted before running: the parity gate reds naming the input, while both
existing self-tests and the full check:i18n-keys run stay green — a divergence
invisible to everything except the new gate. A spelling neither self-test pins
was chosen deliberately (a hyphenated hole name), by widening
CANONICAL_HOLE_NAME to /^[A-Za-z0-9_-]+$/ on ONE side.

run gate side mutated i18n side mutated
check-i18n-call-site-keys.test.ts (130 cases) exit 0 exit 0
fallback-placeholder-spelling-3512.test.ts (18 cases) exit 0 exit 0
pnpm check:i18n-keys (full run) exit 0 exit 0
placeholder-spelling-parity.test.ts exit 1 exit 1

The failure, from the first leg:

  "{{-name}}"
    check-i18n-call-site-keys.mjs  -> []
    placeholder-spelling-rule.ts   -> ["\"{{-name}}\" — the {{- x}} unescape prefix; …"]
  "{{a-b}}"
    check-i18n-call-site-keys.mjs  -> []
    placeholder-spelling-rule.ts   -> ["\"{{a-b}}\" — a non-identifier placeholder name; …"]

{{-name}} was not predicted and is worth reading twice: one edit to a
character class silently retired the unescape-prefix dialect in its UNSPACED
form, which the i18n suite pins only as {{- name}}. That is the drift class
this card was filed about, reproduced, and caught only here. The mirrored leg
prints the same two inputs with the verdicts on the other side.

Restores are proven by state, not by exit code: git hash-object back to the
HEAD blob, git diff HEAD empty, git status clean, and the parity gate green
again. The first mutation attempt did not land on disk (a perl -0777 quoting
miss); the harness's own hash comparison refused it as a no-op rather than
reporting a result, and it was redone.

Boundaries held

  • ⛔ No new spelling, no new exemption, no change to any verdict the rule makes
    today. pnpm check:i18n-keys output is byte-identical to its output on
    4bb33d7, counter for counter (diff clean) — including "Inline default
    spelling: 1031 default(s) … 38 … 25".
  • Each side's existing self-test stays where it is and is unchanged: the four
    i18next-only spellings plus both out-of-range classes in the i18n suite, the
    class-7 cases in scripts/__tests__/.
  • The extracted module sits under __tests__/, excluded from this package's
    build by directory — check:published-tsconfig-exclude green, nothing reaches
    dist.

Gates

Green at 77de761: check:i18n-keys (byte-identical to base) · check:i18n-drift ·
check:i18n-dead-keys (report) · vitest scripts/__tests__/check-i18n-call-site-keys.test.ts

  • the parity test (134) · vitest packages/i18n/ (62 files, 1031) ·
    type-check:scripts · --filter @object-ui/i18n type-check and lint ·
    lint:root (whole scope, 0 errors) · check:entry-guard · check:esm-specifiers ·
    check:control-bytes + a grep -naP self-scan · check:published-tsconfig-exclude ·
    check-changeset-presence · check-governed-queue-guard --test on all five paths
    (NOT GOVERNED).

The i18n type-check is proven non-vacuous rather than assumed: tsc -p tsconfig.test.json --listFiles lists placeholder-spelling-rule.ts among its
653 program inputs.

One declared narrowing. node scripts/check-node-esm-load.mjs (the full load
leg) exits 1 here for a reason that is not this diff: it builds every published
package, turbo shares one cache across worktrees, and its provenance leg refuses
to grade artifacts another tree produced — it named 8 entries coming from
objectui-issue-5174-b20 and objectui-issue-7834. Zero load-leg findings on
the 29 it did grade. --force-build would rebuild 43 packages and repopulate
that shared cache underneath the agents using it, so it was not run; the cheap
--specifiers-only leg is green over the whole repository, and this diff reaches
no published artifact (scripts/ ships in no package, packages/i18n's
__tests__/ is excluded from its build).

Patch round — 589fd4b

CI Test (shard 4/4) was red on 77de761, in this diff:
scripts/__tests__/scripts-type-check.test.ts pins that no root file of
tsconfig.scripts.json's program names an @object-ui/* specifier
— the
premise that lets ci.yml run pnpm type-check:scripts in the cheap half of
the job, above the build. The parity test spelled the defaults-table walk
@object-ui/test-support/defaults-table-scan, and the pin's AST walk reported
exactly that one specifier. The relative packages/i18n/src/locales import on
the line above was not counted — the matcher filters on
startsWith('@object-ui/') — and correctly so: a relative import compiles the
package's source directly and needs nothing built.

Fixed at the import, not at the pin: the same module is now reached as
../../packages/test-support/src/defaults-table-scan, which is the very file
that package's exports maps the specifier to. Same corpus, same floors, same
counts. The pin is green (15/15), and so is the whole directory the first round
should have run: pnpm exec vitest run scripts/__tests__/111 files, 3327
tests
.

One correction to the record, because it is easy to misread this pin as a
build-need measurement: with no package dist directory on disk at all,
pnpm type-check:scripts was green with the workspace specifier too, because
@object-ui/test-support's exports point at ./src/*.ts rather than a build
output. The specifier cost nothing today; what it did was quietly retire the
guarantee that the next @object-ui/* import — one that does need a build —
would be caught. That is exactly what a premise pin is for, and it did its job.

Neither packages/i18n/src/locales nor the defaults-table walk imports anything
beyond node builtins and typescript, so no part of this test needs a built
declaration.

(A second, self-inflicted lesson landed in the same round: the first draft of
the explanatory comment above contained a glob ending in a star-slash, which
closes a block comment. tsc and the pin both read the file fine — the pin
walks it as text — and only the vitest transform caught it. It is the same trap
tsconfig.scripts.json's own header records, and it is why the whole-directory
run is the one that counts.)


Generated by Claude Code

…ons to each other

objectui#3512's rule is implemented twice — `placeholderViolations` for the
copy tables and `check-i18n-call-site-keys.mjs` class 7 for the inline
`defaultValue` call-site arguments. The copies were byte-equivalent and each
named the other, but that is a promise kept by whoever remembers to read both,
not a mechanism, and two implementations of one contract drift the first time
one side learns a spelling or an exemption.

Merging them was measured first, as the card ordered, and both directions are
closed:

  - `@object-ui/i18n` does not resolve from `scripts/`: `tsc -p
    tsconfig.scripts.json` answers TS2307 and node answers ERR_MODULE_NOT_FOUND,
    while the same probe resolves `@object-ui/test-support` clean. Its `exports`
    also point at a `dist/` no fresh checkout has built.
  - A shared `.mjs` under `scripts/` type-checks there and fails in
    `packages/i18n`, whose `tsconfig.test.json` has no `allowJs` (TS7016 plus
    cascading TS7006s) and is deliberately fenced.

So the duplication stays and a parity gate makes it honest: one corpus — both
self-tests' cases, a grammar matrix over every dialect and boundary, all 29,638
locale-pack string leaves and all 1,072 discovered defaults rows, 23,420
distinct inputs — through both implementations, failing on the input they
disagree on. The rule moves out of the `.test.ts` into a module beside it
because importing a test file registers its cases into the importer (measured:
a one-case probe reported nineteen).

No rule change: no new spelling, no new exemption, and `check:i18n-keys`'
output is byte-identical to its output on the merge base.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3186.0 KB 3191.4 KB
Main entry chunk (gzip) 143.5 KB 350 KB
Entry file index-BO0uFEv5.js
Status PASS

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 497.06KB 113.79KB
core (index.js) 6.96KB 2.79KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 182.08KB 50.62KB
fields (index.js) 242.44KB 61.25KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.16KB 3.68KB
plugin-calendar (index.js) 47.35KB 13.21KB
plugin-charts (index.js) 70.31KB 19.62KB
plugin-chatbot (index.js) 193.53KB 46.05KB
plugin-dashboard (index.js) 131.41KB 34.43KB
plugin-designer (index.js) 211.51KB 43.01KB
plugin-detail (index.js) 247.59KB 63.48KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 131.01KB 32.32KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 208.56KB 56.63KB
plugin-kanban (index.js) 52.30KB 14.49KB
plugin-list (index.js) 113.24KB 27.66KB
plugin-map (index.js) 20.35KB 6.77KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 29.95KB 8.67KB
plugin-tree (index.js) 9.16KB 3.18KB
plugin-view (index.js) 84.33KB 20.75KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 10.35KB 3.60KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

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

Copy link
Copy Markdown
Contributor

Standing-down note — Live E2E (informational) is red on main and on every branch today: the upstream base-red anchored on #7990 (objectstack#16186). It is continue-on-error by design, never blocks the queue, and touches nothing this diff changes (a test-side rule module, a parity test, a docblock, one empty changeset). Not this PR's to fix; no re-run requested. ACCEPTed on the card (#7310); flip and arming follow once the remaining checks conclude.


Generated by Claude Code

…ifier

`scripts/__tests__/scripts-type-check.test.ts` pins that no root file of
`tsconfig.scripts.json`'s program names an `@object-ui/*` specifier — the
premise that lets `ci.yml` run `pnpm type-check:scripts` in the cheap half of
the job, above the build. The parity test spelled the defaults-table walk
`@object-ui/test-support/defaults-table-scan` and failed it.

Fixed at the import, not at the pin: the same module is reached as
`../../packages/test-support/src/defaults-table-scan`, exactly as the locale
packs already were. Same file (that package's `exports` maps the specifier to
`./src/defaults-table-scan.ts`), same corpus, same floors.

Worth recording what the pin is, because it is easy to misread as a
build-need measurement: with no package `dist` on disk at all,
`pnpm type-check:scripts` was green with the workspace specifier too — that
package's `exports` point at source. The specifier cost nothing today and
would have quietly retired the guarantee the next `@object-ui/*` import does
cost. Neither `packages/i18n/src/locales` nor the defaults-table walk imports
anything beyond node builtins and `typescript`, so nothing here needs a built
declaration.

Whole suite, not the two files the first round ran:
`pnpm exec vitest run scripts/__tests__/` — 111 files, 3327 tests, green.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3186.0 KB 3191.4 KB
Main entry chunk (gzip) 143.5 KB 350 KB
Entry file index-BO0uFEv5.js
Status PASS

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 497.06KB 113.79KB
core (index.js) 6.96KB 2.79KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 182.08KB 50.62KB
fields (index.js) 242.44KB 61.25KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.16KB 3.68KB
plugin-calendar (index.js) 47.35KB 13.21KB
plugin-charts (index.js) 70.31KB 19.62KB
plugin-chatbot (index.js) 193.53KB 46.05KB
plugin-dashboard (index.js) 131.41KB 34.43KB
plugin-designer (index.js) 211.51KB 43.01KB
plugin-detail (index.js) 247.59KB 63.48KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 131.01KB 32.32KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 208.56KB 56.63KB
plugin-kanban (index.js) 52.30KB 14.49KB
plugin-list (index.js) 113.24KB 27.66KB
plugin-map (index.js) 20.35KB 6.77KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 29.95KB 8.67KB
plugin-tree (index.js) 9.16KB 3.18KB
plugin-view (index.js) 84.33KB 20.75KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 10.35KB 3.60KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

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

@baozhoutao
baozhoutao marked this pull request as ready for review September 6, 2026 12:07
@baozhoutao
baozhoutao added this pull request to the merge queue Sep 6, 2026
Merged via the queue into main with commit f004dae Sep 6, 2026
33 of 34 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-7310-placeholder-spelling-parity branch September 6, 2026 12:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants