Skip to content

fix(i18n): one ellipsis glyph across the ten packs, es usted single-form, pt de-contraction (#3878 #3875 #3877) - #4378

Merged
yinlianghui merged 3 commits into
mainfrom
claude/issue-3878-i18n-copy-trio
Aug 11, 2026
Merged

fix(i18n): one ellipsis glyph across the ten packs, es usted single-form, pt de-contraction (#3878 #3875 #3877)#4378
yinlianghui merged 3 commits into
mainfrom
claude/issue-3878-i18n-copy-trio

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes#3878
Fixes#3875
Fixes#3877

Three locale-copy defects on one surface (packages/i18n/src/locales/*.ts), dispatched as one card. All three are value defects: every key name, key set and placeholder shape was already correct, which is exactly why the three script gates were green on all of them.


1. One ellipsis glyph across the ten packs (#3878)

Ruling applied: converge on the typographic ellipsis (U+2026), per the maintainer-authorized consistency pass registered on objectstack#6015 (2026-08-09). Not a fresh call — this PR executes it.

en ended 33 values with three ASCII full stops and 110 with , and the nine translation packs had copied en value by value, so the split propagated rather than being a translation habit. It is same-screen visible: common.loading (Loading...) renders on the same dashboard as dashboard.loading (Loading…), and console.ai.askAnything sits in the AI panel beside siblings that already used .

312 pack values changed — 34 in en, 278 across the nine. The rewrite ran through the TypeScript AST over value string literals only, so keys, code and the interpolation placeholders were structurally out of its reach; git diff --stat is 312 insertions and 312 deletions, one line per value.

Census corrections the card did not have

The card measured 32 en values on 2026-08-08. On origin/main today it is 33 trailing, plus findings the card's trailing-only census could not see:

what it iswhy it matters
appManagement.searchPlaceholdera 33rd trailing value, added since the cardin scope, converged
collaboration.commentPlaceholderellipsis is mid-sentence (Add a comment... (use @ to mention)), and all nine packs already wrote a trailing-only rule cannot see it, and it renders in the same composer as collaboration.replyingTo
list.loadingthe mirror image: en was already , all nine packs were still ASCIIen never changed, so the drift gate had no event to fire on and the nine were free to sit there

Those two are why the pin below is wider than the card asked for.

The convention is pinned

New: packages/i18n/src/__tests__/ellipsis-glyph-3878.test.ts. It fails, by key name, on any value in any of the ten packs holding three ASCII full stops, plus a complement assertion that the 35 converged keys still hold an ellipsis at all (so deleting the ellipsis cannot pass the ASCII scan by producing nothing), and a placeholder-integrity check.

The card asked for "a trailing ASCII ... in an en value errors". The scan is deliberately wider on both axes because, per the table above, the narrow rule would have shipped with two holes in it. It also needs no per-key list: a new value arriving with a typewriter ellipsis fails without anyone editing the file. Same reasoning and same final shape as de-quote-pairing-3876.test.ts, whose per-key list became "no U+0022 in the de pack at all".

Pre-fix RED, post-fix GREEN

The pin was written and run before the value changes, against unmodified packs:

 ❯ packages/i18n/src/__tests__/ellipsis-glyph-3878.test.ts (4 tests | 4 failed)
× holds no ASCII "..." in any value of any pack
× keeps an ellipsis in every pack for each of the 35 keys this pass converged
× pins the four keys where en was the sole outlier, because they are the drift waivers
× leaves the interpolation placeholders untouched
AssertionError: Write the ellipsis as … (U+2026), not as three ASCII full stops —
objectui#3878, per the consistency pass on objectstack#6015. 312 value(s) still use
the typewriter form.: expected [ …(312) ] to deeply equal []
+ "en common.loading: \"Loading...\"",
+ "en common.select: \"Select...\"",
…
Test Files 1 failed (1)
Tests 4 failed (4)

Captured to a file, not piped through head — the count in that message is read from the full output.

The drift cascade, and five honest waivers

scripts/check-i18n-en-drift.mjs did exactly what the card predicted — that cascade is the mechanism working. It reported 5 keys where en moved and a pack did not:

5 en string(s) changed without their translations:
search.placeholder unchanged in: zh
collaboration.replyingTo unchanged in: zh, ja, ko, de, fr, es, pt, ru, ar
collaboration.replyingToComment unchanged in: zh, ja, ko, de, fr, es, pt, ru, ar
collaboration.commentPlaceholder unchanged in: zh, ja, ko, de, fr, es, pt, ru, ar
appManagement.searchPlaceholder unchanged in: zh, ja, ko, de, fr, es, pt, ru, ar

On these five the packs were already right and en was the outlier — the inverse of the drift this gate hunts, and the one shape its waiver ledger exists for ("if a translation genuinely does not need to change, say so"). Five entries added to scripts/i18n-en-drift-baseline.json, each transcribing its new sentence verbatim so it expires the next time the key changes, and WAIVER_CEILING raised 0 → 5 in scripts/__tests__/check-i18n-en-drift.test.ts — the ratchet that makes growing the ledger a reviewed act. Gate now exits 0 with 5 finding(s) waived by the ledger. The other 269 pack/key pairs followed normally.

Two duplicate-copy surfaces moved with the packs — both required, neither optional

The card warned about the #4294 "11th copy" trap in LIST_DEFAULT_TRANSLATIONS. That specific table is clean (list.loading / list.refreshing were already , and it holds no copy of any of the 35 keys), but the trap exists twice elsewhere, and in both places an existing gate or contract forces the copy to follow:

Eleven inline defaultValue call sites.scripts/check-i18n-call-site-keys.mjs carries a byte-exact rule (objectui#3810) and went red on 11 defaults across 8 keys the moment en moved. Its own instruction is to fix it at the call site — "copy the en value in byte-for-byte, ellipsis and capitalisation included. Do NOT edit packages/i18n/src/locales/en.ts to match the call site". Done, in 7 files under apps/console/, packages/app-shell/ and packages/fields/. Gate back to exit 0, literal inline defaults matching 878 → 889.

Fifteen module-local no-provider fallback entries, in useCollaborationTranslation, useFieldTranslation, useDetailTranslation, ObjectGrid, KanbanImpl, data-table and ConnectionStatus. These render when no LocalizationProvider is mounted, and each map's docblock requires it to stay byte-identical to the en pack — for the collaboration map that is mechanically enforced (objectui#3440, presence-avatars-i18n.test.tsx asserts the map deep-equals en.collaboration), and that test went red on this change, which is how the requirement was found rather than assumed. Leaving the others behind would have left the provider-less path disagreeing with the provider path on ten keys while each file's own header said it must not. useFieldTranslation was already internally split — 'lookup.loading': 'Loading…' sat two lines under 'common.loading': 'Loading...'.

⚠️packages/i18n/src/useSafeTranslation.ts was not touched (#3865 in flight); nor was content/docs/** (#3857/#3859) or packages/data-objectstack/** (#4363).

Exclusions

No en value was excluded — none of the 34 uses ... as load-bearing syntax; all 34 are user-facing ellipsis on placeholders, loading text and progress buttons, and the {{agent}} / {{name}} holes are asserted untouched.

Left alone on purpose, filed rather than smuggled in: ASCII ellipsis that is not a copy of a pack value — ListView.tsx's t('list.search') + '...' concatenation (#4375), the metadata-admin designer's own en/zh table (#4377), and the hardcoded key-less component defaults (combobox, select, loading, KanbanEnhanced, LayoutRenderer), which are instances of the already-open hardcoded-English family (#4028 and siblings) and were not filed again as a twin. The new pin's header records this scope explicitly, so its green is not read as a claim about those surfaces.


2. es draft-preview empty state speaks usted (#3875)

preview.empty.notReadyDescription said Revisa la conversación — the tú imperative — inside a namespace that is 23:1 usted, and it renders underneath the usted DraftPreviewBar at the same moment rather than before or after it. One token: RevisaRevise. The rest of the sentence is third-person statement and carries no register.

Scope held to that one value, per the card's own warning: approvalsInbox is a legitimately tú neighbourhood and was not touched, and no regex register sweep was run (the card documents five false positives that one produces).

3. pt no longer contracts de onto its own hole (#3877)

Direction A, as ruled. ConcurrentUpdateDialog.tsx:124 splits detail.concurrentUpdateDescription on the literal {{field}} into a before/after pair and renders the bolded label in the gap. pt left a bare de in front of that gap, so when the multi-field branch passes the record label (detail.concurrentUpdateRecordLabel = este registro) a Portuguese user read:

uma versão mais recente de este registro durante sua edição
^^^^^^^^^ de + este must contract to deste

No spelling of the leaf could fix it — deste registro renders de deste registro — because the preposition lives in the outer sentence.

Before:Outro usuário salvou uma versão mais recente de {{field}} durante sua edição. Escolha como resolver o conflito.

After:Outro usuário salvou, durante sua edição, uma versão mais recente que afeta {{field}}. Escolha como resolver o conflito.

One deviation from the ruling's illustrative example, on purpose

The ruling offered …do seguinte campo: {{field}}… as an example. This PR does not use it, for two measured reasons:

  1. It is wrong for half the inputs. The hole takes a field label or the record label. do seguinte campo: este registro reads "of the following field: this record", and the multi-field branch is precisely the record-level summary — so that phrasing mis-describes exactly the case the bug is about.
  2. It keeps the defect class alive. Any preposition before the hole re-creates the same bug in a different letter: em + este contracts to neste, a + aquele to àquele, de + o to do. Putting the verbafeta in front of the hole means no contraction rule can apply to whatever the leaf holds, now or later.

The ruling's example was prefixed "e.g."; the binding constraints — hole not preceded by a bare de, {{field}} exactly once, pt-only — are all met, and the exactly-once split contract is now asserted in the pin test.

Single-locale divergence justified: the nine other packs need no rewrite because none of them contracts across this boundary (es de este, fr de cet, de dative von diesem, ru genitive этой записи are all correct as written), and en is unchanged so the drift gate has no event here. pt alone diverges in sentence shape; that is cheaper than nine gratuitous rewrites.

#3546 slice 7 has landed — the current-truth pin is flipped

Contrary to the triage note, packages/i18n/src/__tests__/residue-namespaces-3546.test.tsxdoes exist on main and did pin mais recente de este registro as current truth. Flipped in this change to mais recente que afeta este registro, with an added negative assertion that de este cannot come back, and the comment rewritten to explain the verb choice.

Two more assertions in that file and in i18n.test.ts pinned the pre-ruling ASCII spellings and moved with it. One is worth naming: slice 7 asserted that dashboard.loading and common.loading were different strings — that difference was#3878, and it is why slice 7 could not reuse the neighbour's translations. It is now an equality, kept rather than deleted because it is the only place the convergence is visible from the slice-7 side.


Verification

commandresult
pnpm exec vitest run packages/i18n/ scripts/__tests__/check-i18n-en-drift.test.ts43 files, 821 passed
pnpm exec vitest run (full repo, repo root per AGENTS.md)reported in a follow-up comment
node scripts/check-i18n-en-drift.mjsexit 0 — 34 en changed, 269 followed, 5 waived
node scripts/check-i18n-call-site-keys.mjsexit 0 — 889/892 literal defaults match
node scripts/check-changeset-presence.mjsexit 0 — 1 changeset for 9 released packages
node scripts/check-changeset-no-major.mjsexit 0
node scripts/check-control-bytes.mjsexit 0 — 4084 files scanned

Changeset: .changeset/i18n-copy-trio-3878-3875-3877.md, patch (never major, per the version-alignment rule) for the nine released packages the presence gate names.

Out-of-scope findings filed

Deliberately not filed: the hardcoded key-less component-default placeholders. Searched first — they are instances of the open hardcoded-English family (#4028 and siblings), and a fresh card would have been a twin.


Generated by Claude Code

…action (#3878#3875#3877)
Three locale-copy defects on one surface, all value-domain and therefore
invisible to every key-shaped gate.
#3878 — converge the ellipsis on U+2026 per the consistency pass registered
on objectstack#6015. `en` ended 33 values with three ASCII full stops and 110
with `…`, and the nine packs had copied `en` value by value, so a user read
both glyphs on one screen. 312 pack values rewritten (34 en, 278 across the
nine) via the TypeScript AST over value string literals only, so keys, code
and the `{{name}}`/`{{agent}}` holes were structurally out of reach.
Pinned by packages/i18n/src/__tests__/ellipsis-glyph-3878.test.ts, which
fails by key name on any ASCII `...` in any of the ten packs. Wider than the
card asked for on purpose: `collaboration.commentPlaceholder` puts the
ellipsis mid-sentence and `list.loading` had the packs wrong while `en` was
already right — a trailing-only, en-only rule sees neither.
The drift gate cascaded as predicted and reported 5 keys where `en` moved
with nothing for the packs to follow, because on those the packs were ALREADY
correct. Five ledger waivers added, each transcribing its new sentence, and
WAIVER_CEILING raised 0 -> 5.
Eleven inline `defaultValue` call sites and fifteen module-local no-provider
fallback entries were re-synchronised with the new `en` text; both are
required, not optional — check-i18n-call-site-keys.mjs enforces the first
byte-for-byte and objectui#3440 enforces the collaboration map's parity.
#3875 — es `preview.empty.notReadyDescription`: `Revisa` -> `Revise`. The one
tú imperative in a 23:1 usted namespace, rendering underneath the usted draft
bar at the same moment. `approvalsInbox` is legitimately tú and untouched.
#3877 — pt `detail.concurrentUpdateDescription` left a bare `de` in front of
the `{{field}}` hole, so the multi-field branch rendered `de este registro`
where Portuguese must contract to `deste`. The outer sentence is rewritten so
the hole follows the VERB `afeta`: swapping `de` for `em` or `a` would have
re-created the same defect with a different contraction. pt only; `en`
unchanged, so no drift event. The #3546 slice-7 current-truth pin is flipped
in the same change, along with the slice-7 assertion that `dashboard.loading`
and `common.loading` were different strings — that difference WAS this bug.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3
@vercel

vercelBot commented Aug 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectuiIgnoredIgnoredAug 11, 2026 11:36pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)24.7 KB350 KB
Entry fileindex-l-Vrd1D_.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)9.56KB3.59KB
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)22.10KB4.37KB
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)35.76KB9.11KB
auth (createAuthenticatedFetch.js)4.37KB1.69KB
auth (index.js)2.35KB1.07KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)4.91KB0.87KB
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)489.12KB108.41KB
core (index.js)2.99KB1.14KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)151.72KB40.42KB
fields (index.js)228.37KB56.62KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.32KB1.77KB
i18n (index.js)3.35KB1.38KB
i18n (pickLocalized.js)3.69KB1.73KB
i18n (provider.js)23.12KB7.62KB
i18n (useDisplayLocale.js)2.33KB1.20KB
i18n (useObjectLabel.js)27.59KB6.63KB
i18n (useSafeTranslation.js)7.77KB3.13KB
layout (index.js)38.98KB10.85KB
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)8.75KB3.06KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)3.67KB1.12KB
permissions (evaluator.js)4.41KB1.44KB
permissions (index.js)0.91KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.52KB
permissions (usePermissions.js)1.55KB0.71KB
plugin-ai (index.js)15.71KB3.79KB
plugin-calendar (index.js)45.23KB12.45KB
plugin-charts (index.js)62.18KB17.67KB
plugin-chatbot (index.js)180.33KB42.79KB
plugin-dashboard (index.js)121.60KB31.63KB
plugin-designer (index.js)210.91KB42.67KB
plugin-detail (index.js)239.00KB59.76KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)114.58KB27.68KB
plugin-gantt (index.js)164.14KB39.98KB
plugin-grid (index.js)188.00KB49.93KB
plugin-kanban (index.js)48.60KB13.41KB
plugin-list (index.js)110.10KB26.74KB
plugin-map (index.js)18.05KB5.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)40.60KB10.58KB
plugin-timeline (index.js)26.21KB7.52KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.03KB20.55KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.71KB3.53KB
providers (index.js)0.44KB0.22KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.67KB2.37KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)23.71KB7.96KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.23KB0.66KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)4.09KB1.74KB
sdui-parser (index.js)4.47KB2.03KB
sdui-parser (parse.js)10.04KB2.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)4.69KB1.48KB
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.05KB1.52KB
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

… copy (#3878)
Eight test files asserted a rendered string that objectui#3878 moved to U+2026 —
either the en pack value under a provider, or the module-local no-provider
fallback map, which is byte-identical to it by contract. Caught by CI shards
1-3 and by the local run; each one is the pinned copy following its source, not
a weakened assertion.
data-table-manual-search table.search placeholder selector
serverSearch (plugin-grid) table.search placeholder selector
complex-widgets (fields) table.search x2
LookupField.hydration common.select negative assertion —
its own comment already
said "Select…" while the
assertion said ASCII
RecordActivityTimeline.loading detail.loading
record-chatter.loading detail.loading
ConversationsSidebar console.ai.searchChats
comment-thread-no-provider collaboration.replyingTo / commentPlaceholder
Deliberately NOT moved: literals that are the test's or component's own
hardcoded English with no pack key behind them (AuthProvider's loadingFallback
prop, the plugin-designer grid mock, combobox/select/loading/command defaults),
and the two docblocks that quote the retired spelling as history.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)24.7 KB350 KB
Entry fileindex-l-Vrd1D_.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)9.56KB3.59KB
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)22.10KB4.37KB
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)35.76KB9.11KB
auth (createAuthenticatedFetch.js)4.37KB1.69KB
auth (index.js)2.35KB1.07KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)4.91KB0.87KB
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)489.12KB108.41KB
core (index.js)2.99KB1.14KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)151.72KB40.42KB
fields (index.js)228.37KB56.62KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.32KB1.77KB
i18n (index.js)3.35KB1.38KB
i18n (pickLocalized.js)3.69KB1.73KB
i18n (provider.js)23.12KB7.62KB
i18n (useDisplayLocale.js)2.33KB1.20KB
i18n (useObjectLabel.js)27.59KB6.63KB
i18n (useSafeTranslation.js)7.77KB3.13KB
layout (index.js)38.98KB10.85KB
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)8.75KB3.06KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)3.67KB1.12KB
permissions (evaluator.js)4.41KB1.44KB
permissions (index.js)0.91KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.52KB
permissions (usePermissions.js)1.55KB0.71KB
plugin-ai (index.js)15.71KB3.79KB
plugin-calendar (index.js)45.23KB12.45KB
plugin-charts (index.js)62.18KB17.67KB
plugin-chatbot (index.js)180.33KB42.79KB
plugin-dashboard (index.js)121.60KB31.63KB
plugin-designer (index.js)210.91KB42.67KB
plugin-detail (index.js)239.00KB59.76KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)114.58KB27.68KB
plugin-gantt (index.js)164.14KB39.98KB
plugin-grid (index.js)188.00KB49.93KB
plugin-kanban (index.js)48.60KB13.41KB
plugin-list (index.js)110.10KB26.74KB
plugin-map (index.js)18.05KB5.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)40.60KB10.58KB
plugin-timeline (index.js)26.21KB7.52KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.03KB20.55KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.71KB3.53KB
providers (index.js)0.44KB0.22KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.67KB2.37KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)23.71KB7.96KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.23KB0.66KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)4.09KB1.74KB
sdui-parser (index.js)4.47KB2.03KB
sdui-parser (parse.js)10.04KB2.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)4.69KB1.48KB
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.05KB1.52KB
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

…is concatenated in code, not a pack value (#3878)
Correcting my own over-reach in the previous commit. The two LookupField cases
in complex-widgets.test.tsx assert `getByPlaceholderText('Search...')`, and I
read them as pinning `useFieldTranslation`'s `'table.search'` default, which
objectui#3878 moved to U+2026. They are not: `LookupField.tsx:1085` builds the
placeholder as
placeholder={t('common.search') + '...'}
so the ellipsis is a literal concatenated at the call site and never came from
the pack at all. Moving the assertions turned both tests red — caught by the
local targeted run before the CI shard reported it.
Reverted to ASCII, with a comment at the assertion naming line 1085 and
objectui#4375 (filed for this and the identical ListView.tsx:2829 concatenation)
so the next reader does not repeat the lap. The other seven files in the
previous commit are unaffected: data-table.tsx:1593 and
ConversationsSidebar.tsx:251 read `t(key)` directly, so their copy really did
move with the pack.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)24.7 KB350 KB
Entry fileindex-l-Vrd1D_.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)9.56KB3.59KB
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)22.10KB4.37KB
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)35.76KB9.11KB
auth (createAuthenticatedFetch.js)4.37KB1.69KB
auth (index.js)2.35KB1.07KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)4.91KB0.87KB
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)489.12KB108.41KB
core (index.js)2.99KB1.14KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)151.72KB40.42KB
fields (index.js)228.37KB56.62KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.32KB1.77KB
i18n (index.js)3.35KB1.38KB
i18n (pickLocalized.js)3.69KB1.73KB
i18n (provider.js)23.12KB7.62KB
i18n (useDisplayLocale.js)2.33KB1.20KB
i18n (useObjectLabel.js)27.59KB6.63KB
i18n (useSafeTranslation.js)7.77KB3.13KB
layout (index.js)38.98KB10.85KB
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)8.75KB3.06KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)3.67KB1.12KB
permissions (evaluator.js)4.41KB1.44KB
permissions (index.js)0.91KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.52KB
permissions (usePermissions.js)1.55KB0.71KB
plugin-ai (index.js)15.71KB3.79KB
plugin-calendar (index.js)45.23KB12.45KB
plugin-charts (index.js)62.18KB17.67KB
plugin-chatbot (index.js)180.33KB42.79KB
plugin-dashboard (index.js)121.60KB31.63KB
plugin-designer (index.js)210.91KB42.67KB
plugin-detail (index.js)239.00KB59.76KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)114.58KB27.68KB
plugin-gantt (index.js)164.14KB39.98KB
plugin-grid (index.js)188.00KB49.93KB
plugin-kanban (index.js)48.60KB13.41KB
plugin-list (index.js)110.10KB26.74KB
plugin-map (index.js)18.05KB5.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)40.60KB10.58KB
plugin-timeline (index.js)26.21KB7.52KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.03KB20.55KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.71KB3.53KB
providers (index.js)0.44KB0.22KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.67KB2.37KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)23.71KB7.96KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.23KB0.66KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)4.09KB1.74KB
sdui-parser (index.js)4.47KB2.03KB
sdui-parser (parse.js)10.04KB2.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)4.69KB1.48KB
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.05KB1.52KB
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

@yinlianghuiClaude

Copy link
Copy Markdown
CollaboratorAuthor

Verification, and one correction worth reading

The correction: Search... in a fields test is ambiguous evidence

The second commit moved eight test files onto the converged copy. Two of those moves were wrong, and the third commit reverts them — recorded here because the trap is not obvious and cost a CI lap.

packages/fields/src/complex-widgets.test.tsx asserts getByPlaceholderText('Search...') against a rendered LookupField. packages/fields/src/widgets/useFieldTranslation.ts carries 'table.search': 'Search...' in its no-provider defaults map, which this PR converged — so those assertions read exactly like they pin that map. They do not:

packages/fields/src/widgets/LookupField.tsx:1085placeholder={t('common.search')+'...'}

The ellipsis is a literal concatenated at the call site, from common.search ('Search', no dots). It never came from the pack and did not move. Moving the assertions turned both tests red; the local targeted run caught it before the CI shard reported it.

So the two spellings are not interchangeable evidence: a Search... in a fields test may pin the defaults map (moves with the pack) or this concatenation (does not). The reverted assertions now carry a comment naming line 1085 and #4375, which is filed for this and the identical ListView.tsx:2829.

The other seven files are unaffected — data-table.tsx:1593 and ConversationsSidebar.tsx:251 read t(key) directly, so their copy really did move.

Script gates, on the final tree

commandresult
node scripts/check-i18n-en-drift.mjsexit 0 — 34 en changed, 269 pack values followed, 5 waived by the ledger
node scripts/check-i18n-call-site-keys.mjsexit 0 — 889/892 literal inline defaults match their en value (878 before)
node scripts/check-changeset-presence.mjsexit 0 — 1 changeset for 9 released packages
node scripts/check-changeset-no-major.mjsexit 0
node scripts/check-control-bytes.mjsexit 0 — 4086 tracked text files scanned
ASCII-ellipsis census over the ten packs0 values remaining (312 before)

The new pin, both directions

Written and run before the value changes, against unmodified packs: 4 of 4 tests red, naming all 312 offending values. After: packages/i18n/ plus the drift-gate suite at 43 files / 821 tests passed.

#3877's flipped assertions, both directions

The #3546 slice-7 pin was flipped to the new pt sentence. Composing the template with detail.concurrentUpdateRecordLabel exactly as ConcurrentUpdateDialog.tsx:124 does:

OLD (origin/main): …uma versão mais recente de este registro durante sua edição.
toContain('mais recente que afeta este registro') FAIL
not.toContain('de este') FAIL
template toContain('que afeta {{field}}') FAIL
NEW (this PR): …uma versão mais recente que afeta este registro. Escolha…
all three PASS

Composed across all ten packs, only pt changed shape, and the nine others remain correct as written (es de este registro, fr de cet enregistrement, de von diesem Datensatz, ru этой записи) — which is the measured basis for the single-locale divergence.


Generated by Claude Code

@yinlianghuiClaude

Copy link
Copy Markdown
CollaboratorAuthor

ACCEPT — PM 复核 (session session_017Qqyix2QcnpUC9XeYVDzx3), closes #3878, #3875, #3877.

Flipping ready + arming auto-merge now — per the report's own risk note, this PR's ten-pack surface is worth landing early.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment