Skip to content

fix(i18n): search placeholders become pack values, and four English-serving values get translated (#4375 #4376) - #4391

Merged
yinlianghui merged 5 commits into
mainfrom
claude/issue-4375-4376-i18n-stragglers
Aug 12, 2026
Merged

fix(i18n): search placeholders become pack values, and four English-serving values get translated (#4375 #4376)#4391
yinlianghui merged 5 commits into
mainfrom
claude/issue-4375-4376-i18n-stragglers

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #4375
Fixes #4376

Two post-#3878 i18n stragglers on one shared surface (packages/i18n/src/locales/*.ts), paired so two branches do not race on the ten packs. Both are value-domain defects — every key name, key set and placeholder shape was already correct, which is exactly why all three script gates were green on both.


1. The two search placeholders become pack values (#4375)

Ruling applied: direction A (contract-first). The t(key) + '...' concatenation dies at both call sites.

packages/plugin-list/src/ListView.tsx:2829       t('list.search')   + '...'  →  t('table.search')
packages/fields/src/widgets/LookupField.tsx:1085 t('common.search') + '...'  →  t('table.search')

The key choice: reuse table.search, mint nothing — measured, not taste

The card left "reuse table.search vs mint list.searchPlaceholder / lookup.searchPlaceholder" to the implementer, to be decided by measuring the namespace conventions at each call site. Four measurements, all on origin/main at 3e19fe78a:

(a) table.search is already THE search-input placeholder key in this repo. It is read as a placeholder= at three sites and is read nowhere else:

site read
packages/components/src/renderers/complex/data-table.tsx:1593 placeholder={t('table.search')}
packages/fields/src/widgets/RecordPickerDialog.tsx:1041 placeholder={t('table.search')}
packages/fields/src/widgets/PeoplePicker.tsx:417 placeholder={t('table.search')}

Its en value is Search… — byte-for-byte the string both broken call sites were assembling. Two of those three sites are in packages/fields, reading through useFieldTranslation — the same hook LookupField uses, and RecordPickerDialog / PeoplePicker are its two nearest siblings in the same directory and the same control class (a record-picker popover with a filter input at the top). Reuse at LookupField is not a cross-namespace reach; it converges the one widget in that family that was doing something else.

(b) The two keys the call sites used are bare NOUNS, and one of them must stay that way. list.search = Search, common.search = Search — no ellipsis, by design. list.search is also read three lines up as the trigger button's tooltip:

title={searchTerm ? `${t('list.search')}: ${searchTerm}` : t('list.search')}

So "just put the ellipsis in list.search" was never available — it would have put one on that tooltip too. A pin in ListView.test.tsx now asserts both halves (placeholder Search…, tooltip Search) so the two keys cannot be collapsed later by accident.

(c) The no-provider defaults table already carries the key at one site, and cross-namespace entries are the norm at both.

map carries table.search? already cross-namespace?
FIELD_DEFAULTS (useFieldTranslation.ts) yes'table.search': 'Search…' yes: table.*, common.*, capability.*, fields.*
LIST_DEFAULT_TRANSLATIONS (ListView.tsx) no — added here yes: table.rowsPerPage, grid.toolbar.*, detail.recordDetail*

So LookupField needed no map change at all, and ListView needed one entry, byte-identical to en per that map's docblock contract. The borrow also has a precedent in that same map, with its rationale written down — the detail.recordDetail entry says it is borrowed rather than minted as list.recordDetail because "one heading on one control should not get two translations that can drift apart." That is the identical argument, so the new entry cites it rather than inventing a second convention.

(d) The RTL half of the card is fixed for free, and only by (a). #4375 names ar as the sharpest case: t(…) + '...' appended an LTR ASCII run to the end of a right-to-left string. As a pack value the ellipsis is the ar pack's own (table.search = بحث…), so the Unicode bidi algorithm resolves it as a neutral adjacent to the RTL run and it lands at the logical end. It also restores the thing the card said the concatenation "takes away from the packs entirely": ar can now drop the ellipsis by editing one pack value.

Weighed against minting. Minting list.searchPlaceholder + lookup.searchPlaceholder costs 20 new values across ten packs, each one authored from scratch — 20 new chances to get the glyph or the register wrong — to buy a divergence nobody has asked for; all four surfaces mean "type here to filter the records shown." The values reused here are already translated, already reviewed, already under #3878's U+2026 pin in all ten packs. If the two ever do want to diverge, minting then is cheap and reversible; minting now is a speculative vocabulary surface with no measured pull. Zero new keys.

Test pins moved off the concatenated truth

packages/fields/src/complex-widgets.test.tsx:200 and :469 asserted getByPlaceholderText('Search...'). #3878's branch left a comment at the first one naming LookupField.tsx:1085 and this issue, and saying "when that lands, this moves" — honored: both move to Search…, and the comment is rewritten to record where the string now comes from (a pack value via useFieldTranslation) so the next reader is not sent back down the same path.

Swept for a ListView equivalent: there is none, and that is itself a finding. Every ListView.test.tsx search assertion matches getByPlaceholderText(/search/i), which matches Search... and Search… — so no existing test could see this change in either direction. A byte-exact pin is added rather than assumed (see the reverse verification below, where its absence is measured rather than argued).


2. Four values the packs were serving in English (#4376)

Ruling applied: translate all four, and take the gate.

Per-pack translations — 30 values

Each was written by reading that pack's neighbouring keys first, never by translating the English in isolation.

list.loadingen = Loading records…. zh had already translated it; the other eight served the English sentence verbatim. Each pack's own loading-verb shape and its own established word for "records" (taken from list.recordCount / list.noMatches / lookup.recordCount):

pack new value shape it follows
ja レコードを読み込み中… grid.loading = グリッドを読み込み中…
ko 레코드 로딩 중… grid.loading = 그리드 로딩 중…
de Datensätze werden geladen… grid.loading = Raster wird geladen… (plural subject → werden)
fr Chargement des enregistrements… chart.loading = Chargement du graphique…
es Cargando registros… grid.loading = Cargando cuadrícula…
pt Carregando registros… grid.loading = Carregando grade…
ru Загрузка записей… grid.loading = Загрузка таблицы… (genitive)
ar جارٍ تحميل السجلات… list.refreshing = جارٍ التحديث…

The ar choice is worth stating: the pack spells the progressive two ways (جاري in grid/chart/common, جارٍ in home/dashboard). The sibling in the same namespace, list.refreshing, uses جارٍ, so this follows the namespace rather than the global majority — these two render on the same list view, one after the other.

designer.undo / designer.redo — English in all nine packs. The card flagged these as "the arguable pair" (some product vocabularies keep them English). They are not arguable here, and the measurement settles it rather than my taste: this repo has already made the identical call three times, and the three families agree byte-for-byte in all nine packs.

pack undo redo already spelled this way at
zh 撤销 重做 appDesigner.*, gantt.toolbar.*, workflow.*
ja 元に戻す やり直す same three
ko 실행 취소 다시 실행 same three
de Rückgängig Wiederholen same three
fr Annuler Rétablir same three
es Deshacer Rehacer same three
pt Desfazer Refazer same three
ru Отменить Повторить same three
ar تراجع إعادة same three

So no new vocabulary was invented: designer.* is copied from appDesigner.undo/redo, gantt.toolbar.undo/redo and workflow.undo/redo, which are unanimous. (grid.bulk.undo deliberately diverges in ja/ko取り消す / 되돌리기 — because it undoes a bulk data operation, not an edit-history step. Not copied.)

appDesigner.snakeCaseHinten = Must be snake_case (e.g. my_app). snake_case and my_app are identifiers and stay literal; only the prose around them is translated. Each pack's "e.g." convention is taken from appDesigner.navIconPlaceholder, the sibling in the same namespace that already has a parenthetical example:

pack new value convention source (navIconPlaceholder)
ko snake_case 형식이어야 합니다 (예: my_app) 아이콘 이름 (예: Users)
ru Должно быть в формате snake_case (например, my_app) Имя значка (например, Users)
ar يجب أن يكون بصيغة snake_case (مثل my_app) اسم الأيقونة (مثل Users)
pt Deve ser snake_case (ex. my_app) Nome do ícone (ex. Users)

Deviation from the ruling's letter: pt is a fourth untranslated pack on this key

The ruling says ko/ru/ar, because the card's table says ko/ru/ar. Re-measuring found pt is byte-identical to en on this key too — and the card's own text explains why it could not see that: its probe is scoped to the five non-Latin packs, and it warns in the same paragraph that the probe "is not sound for de/fr/es/pt."

Here the ambiguity the scoping guards against does not exist: de, fr and es all translated this sentence, so pt alone holding a full English sentence with a parenthetical example is an omission by the same evidence the card used for the other three. Translating ko/ru/ar while knowingly leaving pt English would have shipped a defect I had already found, so it is fixed and declared here rather than smuggled or dropped. The new gate does not and cannot cover it — it is non-Latin-scoped by ruling — so this one is a hand fix, and the gate's header says so explicitly rather than letting its green imply otherwise.

The gate (#4118 guard-class: pre-fix RED required)

New: packages/i18n/src/__tests__/untranslated-identity-4376.test.ts.

Home. A dedicated pin file next to ellipsis-glyph-3878.test.ts, not an addition to all-locales-key-parity.test.ts. That file's own header states its boundary — it owns "key sets and placeholder shape, and nothing about what a value SAYS" — so putting a value-domain probe inside it would contradict the boundary it documents. The value-domain pins in this package (ellipsis-glyph-3878, de-quote-pairing-3876, objectView-value-language-3582, viewReadonlyTooltip-semantics-3625) each live in their own file; this follows that convention. It is pack-intrinsic: it reads builtInLocales and nothing else.

Rule. value === en[key] && /[A-Za-z]{4,}/.test(value), over zh/ja/ko/ru/ar only, minus an explicit allowlist. Not extended to de/fr/es/pt, per the ruling and the card's reasoning — identity is genuine there often enough that the allowlist would grow without bound. The {4,} floor keeps acronyms and units (URL, API, ID, CSV, v1) out of the report without an allowlist entry each.

Pre-fix RED, run against unmodified packs before any translation landed (captured to a file, not piped through head) — it names exactly the four defect keys, 17 pack/key pairs, and nothing else:

 ❯ packages/i18n/src/__tests__/untranslated-identity-4376.test.ts (3 tests | 2 failed)
   × holds no untranslated English value in zh/ja/ko/ru/ar outside the allowlist
   × pins the four keys this card repaired, so the fix cannot be reverted quietly

AssertionError: These values are byte-identical to `en` inside a pack whose script is
not Latin, which means they were never translated — objectui#4376. 17 value(s). …
+   "ja list.loading: \"Loading records…\"",
+   "ko list.loading: \"Loading records…\"",
+   "ru list.loading: \"Loading records…\"",
+   "ar list.loading: \"Loading records…\"",
+   "zh designer.undo: \"Undo\"",     (+ ja, ko, ru, ar)
+   "zh designer.redo: \"Redo\"",     (+ ja, ko, ru, ar)
+   "ko appDesigner.snakeCaseHint: \"Must be snake_case (e.g. my_app)\"",   (+ ru, ar)

 Test Files  1 failed (1)
      Tests  2 failed | 1 passed (3)

The third test (keeps the allowlist honest) passed on that same pre-fix run, which is the evidence that the 22 allowlist entries were each a live identity and not padding.

Allowlist — 22 keys, re-measured, each with a one-line reason. The card estimated "~14"; that was a count of distinct values, and the probe reports keys. The census over origin/main at 3e19fe78a returns 26 matching keys: 4 defects + 22 legitimate. Three of the 22 are not enumerated in the card's prose and were found by re-measuring: auth.setup.emailLabel, collaboration.userStatusTitle, organization.settings.logoLabel.

key value reason
layout.metadata.jsonBadge JSON format name, not a word
workflow.webhook Webhook protocol term, untranslated in these packs
workflow.webhookEvent Webhook same term, event-typed
publicForm.poweredBy Powered by ObjectStack product attribution; ja keeps the wordmark line
connectAgent.apiKey.badge headless the literal mode name in the CLI and API
marketplace.pricing.freemium Freemium pricing-tier term of art; ru keeps the loanword
console.settingsHub.beta Beta release-stage badge, kept Latin in zh
console.settingsHub.categories.Beta Beta same badge, reached by category name
appDesigner.logoUrl Logo URL Logo + the URL acronym, used as-is
appDesigner.faviconUrl Favicon URL same; Favicon has no settled translation here
organization.settings.logoLabel Logo zh's own vocabulary — its whole block says Logo 链接, Logo 已上传…
auth.setup.emailLabel Email ru loanword — that block is otherwise fully localized (ООО Пример, name@example.ru), so it is a choice, not an omission
auth.login.emailPlaceholder name@example.com RFC 2606 example address — an identifier
auth.register.emailPlaceholder name@example.com same
auth.forgotPassword.emailPlaceholder name@example.com same
auth.setup.emailPlaceholder name@example.com same (ru localizes it to name@example.ru)
auth.setup.orgNamePlaceholder Acme Inc. placeholder company name; ru localizes it, zh/ja keep it
grid.import.templateFileName {{object}}-import-template a generated filename — must stay ASCII and portable
fields.image.counter {{current}} / {{total}} digits and a slash
marketplace.versionBadge v{{version}} version prefix plus the hole
appManagement.toast.bulkFailureEntry {{name}} ({{reason}}) holes and parentheses only
collaboration.userStatusTitle {{name}} ({{status}}) holes and parentheses only

The allowlist is keyed by key, not key+lang — looser (an entry excuses the identity in all five packs) but it does not churn when another pack legitimately adopts the same proper noun. The looseness is paid for by a second assertion, keeps the allowlist honest, which fails on any entry that is no longer a live identity anywhere or names a key en does not define — so the list cannot quietly accumulate cover for keys it no longer describes. A third assertion pins the four repaired keys as non-empty and non-English, because the scan alone would go green if a value were simply deleted (green because nothing is produced is not the same fact as green because the copy is right — the same complement ellipsis-glyph-3878.test.ts carries, for the same reason).


Verification

command result
pnpm exec vitest run packages/i18n/ packages/plugin-list/ packages/fields/ --maxWorkers=2 154 files, 2552 tests passed — includes #3878's pin, key parity, and the new gate
node scripts/check-i18n-call-site-keys.mjs exit 0 — "Every in-scope call-site key resolves against the en pack (2859 keys)", 889/892 literal inline defaults match
node scripts/check-i18n-en-drift.mjs exit 0 — "0 en value(s) changed (0 key(s) added, 0 removed)… No en value changed in this range."
node scripts/check-control-bytes.mjs exit 0 — 4089 tracked text files scanned
node scripts/check-changeset-presence.mjs exit 0 — 13 source files of 3 released packages, 1 changeset
node scripts/check-changeset-no-major.mjs exit 0
type-check (tsc --noEmit and tsc -p tsconfig.test.json) for i18n, plugin-list, fields exit 0 on all three
eslint on the touched files 0 errors (280 pre-existing no-explicit-any warnings, none introduced)

The drift gate's verdict is 0 keys added, not the additive verdict the dispatch anticipated — that is the direct consequence of the reuse decision in §1(a): no key was minted and no en value moved, so the gate has no event to report. The translations are pack-side only, which this gate is blind to by design; that blindness is precisely what #4376 exists to close, and the new pin is what closes it.

CJK / Cyrillic / Arabic values are not control bytes — check-control-bytes scans control bytes and is green. The touched files were additionally self-scanned with grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]': no matches.

Reverse verification (commit-then-revert, never git stash)

1. Revert LookupField.tsx to the concatenation → the two moved assertions go red, in the predicted direction.

 × sends $search param on search input
 × navigates options with arrow keys and selects with Enter
TestingLibraryElementError: Unable to find an element with the placeholder text of: Search…
 Test Files  1 failed (1)
      Tests  2 failed | 39 passed (41)

Exactly the two that moved; the other 39 in the file stay green, so the revert isolates them rather than sweeping the file.

2. Revert ListView.tsx → the predicted direction here was different, and it was predicted before the run. The pre-existing assertions all use getByPlaceholderText(/search/i), which matches Search... and Search… equally — so no pre-existing test can go red on this call site in either direction. That is the null result, and it is the reason the byte-exact pin above was added rather than assumed. With the pin in place the same revert produces the red, and the sibling /search/i tests stay green in the same run — both facts from one measurement:

 × takes the search placeholder from a pack value, not a code concatenation
TestingLibraryElementError: Unable to find an element with the placeholder text of: Search…
 Test Files  1 failed (1)
      Tests  1 failed | 147 passed (148)

One red — the new pin. 147 green, and those 147 include every pre-existing /search/i search assertion in the file: the null result, measured rather than argued.

3. Delete one new translation → the gate names that key. Reverting ru's list.loading to the English value:

 × holds no untranslated English value in zh/ja/ko/ru/ar outside the allowlist
 × pins the four keys this card repaired, so the fix cannot be reverted quietly

AssertionError: … objectui#4376. 1 value(s). …
+   "ru list.loading: \"Loading records…\"",

AssertionError: ru list.loading is back to the English value:
  expected 'Loading records…' not to be 'Loading records…'

 Test Files  1 failed (1)
      Tests  2 failed | 1 passed (3)

Both the scan and its complement name the key, and nothing else moves.

All three reverts were taken with git checkout / an in-worktree edit and restored from the branch. git stash was never used (objectui#3430 — the stash stack lives in the common .git and is shared across worktrees, so the isolation this worktree gives does not extend to it).

Run under flock /tmp/os-heavy-verify.lock (build and DOM suites), which was contended for most of this task — the queue waits are the lock working, not a hang. Reverse verification 3 is a node-environment suite (one file, ~1.5s) and was run outside the lock with 13.6 GB available, which is declared here rather than left to be inferred.

origin/main moved during the task (#4379, #4382 landed). Merged, not rebased, and no force push; neither commit touches packages/i18n, packages/plugin-list or packages/fields, and the full run above is on the merged tree.


Scope

Held to packages/i18n/src/locales/*.ts, packages/i18n/src/__tests__/**, packages/plugin-list/**, packages/fields/** and the changeset. Not touched, per the in-flight surfaces named at dispatch: packages/core/**, packages/app-shell/** (#4040-T5 — which is why #4377 is not fixed here even though it is the same glyph family), scripts/check-type-check-coverage.mjs, packages/plugin-dashboard/**, packages/plugin-report/** (#4330), packages/plugin-gantt/**, packages/plugin-grid/** (#4370), workflow/script comment files and content/docs/** (#4381).

Left alone deliberately, not filed as twins: the hardcoded key-less placeholders in packages/components/src/custom/combobox.tsx, packages/runner/src/LayoutRenderer.tsx and packages/plugin-designer/src/__tests__/__mocks__/plugin-grid.tsx still spell Search..., but they read no key at all — instances of the open hardcoded-English family (#4028 and siblings), as #4378 already recorded.

Changeset: .changeset/i18n-stragglers-4375-4376.md, patch for the three released packages the presence gate names (never major, per the version-alignment rule).


Generated by Claude Code

claude added 5 commits August 12, 2026 00:06
…tin packs (#4376)

Guard first, RED on the four defect keys and nothing else: 17 pack/key
pairs across zh/ja/ko/ru/ar, with the 22 legitimate identities absorbed
by an explicit allowlist.
#4376)

list.loading in the eight packs that copied en (zh had translated it),
designer.undo/redo in all nine, appDesigner.snakeCaseHint in ko/ru/ar
plus pt — pt is the same omission, invisible to the card's non-Latin
probe but proven by de/fr/es having translated it.

undo/redo copy each pack's own established edit-history vocabulary:
appDesigner.undo/redo, gantt.toolbar.undo/redo and workflow.undo/redo
already agree byte-for-byte in all nine packs, so this was a settled
call, not a fresh one.
…atenations (#4375)

ListView and LookupField built the placeholder as t(key) + '...', so the
ellipsis was a literal in code: ASCII in all ten locales, and no pack
could opt out of it. Both now read table.search — already THE
search-input placeholder key here (data-table, RecordPickerDialog,
PeoplePicker) and already translated with U+2026 in all ten packs.

Zero new keys. The two complex-widgets.test.tsx assertions that pinned
the concatenated truth move to the pack value.
)

Every pre-existing search assertion in this file matches /search/i, which
matches 'Search...' and 'Search…' equally — so nothing here could see
#4375 in either direction. Measured, not assumed: reverting the call site
with only those assertions in place leaves the whole file green.

Also pins that the trigger tooltip keeps the bare noun list.search, which
is why the ellipsis could not simply be added to that key.
@vercel

vercel Bot commented Aug 12, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectui Ignored Ignored Aug 12, 2026 12:56am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 24.7 KB 350 KB
Entry file index-FoxLUD-h.js
Status PASS

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 9.56KB 3.59KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 8.92KB 3.41KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 22.10KB 4.37KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.13KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.64KB 2.21KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 35.76KB 9.11KB
auth (createAuthenticatedFetch.js) 4.37KB 1.69KB
auth (index.js) 2.35KB 1.07KB
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) 4.91KB 0.87KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 26.07KB 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.65KB 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) 489.12KB 108.41KB
core (index.js) 2.99KB 1.14KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 151.72KB 40.42KB
fields (index.js) 228.36KB 56.61KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 3.35KB 1.38KB
i18n (pickLocalized.js) 3.69KB 1.73KB
i18n (provider.js) 23.12KB 7.62KB
i18n (useDisplayLocale.js) 2.33KB 1.20KB
i18n (useObjectLabel.js) 27.59KB 6.63KB
i18n (useSafeTranslation.js) 7.77KB 3.13KB
layout (index.js) 38.98KB 10.85KB
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.74KB
mobile (index.js) 1.50KB 0.62KB
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.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 8.75KB 3.06KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.67KB 1.12KB
permissions (evaluator.js) 4.41KB 1.44KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.55KB 0.71KB
plugin-ai (index.js) 15.71KB 3.79KB
plugin-calendar (index.js) 45.23KB 12.45KB
plugin-charts (index.js) 62.18KB 17.67KB
plugin-chatbot (index.js) 180.33KB 42.79KB
plugin-dashboard (index.js) 121.60KB 31.63KB
plugin-designer (index.js) 210.91KB 42.67KB
plugin-detail (index.js) 239.00KB 59.76KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 114.58KB 27.68KB
plugin-gantt (index.js) 164.14KB 39.98KB
plugin-grid (index.js) 188.00KB 49.93KB
plugin-kanban (index.js) 48.60KB 13.41KB
plugin-list (index.js) 110.12KB 26.74KB
plugin-map (index.js) 18.05KB 5.80KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 40.60KB 10.58KB
plugin-timeline (index.js) 26.21KB 7.52KB
plugin-tree (index.js) 8.50KB 2.88KB
plugin-view (index.js) 84.03KB 20.55KB
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.71KB 3.53KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.67KB 2.37KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 23.71KB 7.96KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.23KB 0.66KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 4.47KB 2.03KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.87KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-retry.js) 4.32KB 2.02KB
types (index.js) 3.05KB 1.52KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
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 (spec-report.js) 5.05KB 1.93KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 3.40KB 1.71KB
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
Collaborator Author

ACCEPT — PM 复核 (session session_017Qqyix2QcnpUC9XeYVDzx3), closes #4375, #4376.

  • Key choice: reuse of table.search at both sites is the ruling's "decided by measurement" done right — four independent measurements, the strongest being that FIELD_DEFAULTS already carried the key and 2 of its 3 existing readers are LookupField's directory siblings. Zero minted keys, RTL fixed for free, and the drift gate's "0 added" verdict is the honest mechanical consequence, not a miss.
  • Gate: dedicated pin file with the 22-key allowlist re-measured (the card's ~14 counted values, not keys — three legitimate identities found beyond its prose), the allowlist-honesty complement paying for key-level granularity, and the guard-before-fix ordering visible in history (gate commit precedes fix commit). Pre-fix red named exactly the four defect keys.
  • Declared deviations all accepted: the pt snakeCaseHint addition is sound evidence-based scope (the probe's own soundness boundary made pt invisible, not exonerated; de/fr/es translating it makes pt's identity an omission), the ListView byte-exact pin fills a measured pinning hole, and the null-direction reverse verification reported as null rather than template-shaped is the standard.
  • Recorded risks acknowledged: designer.undo/redo are translated-but-dormant (wiring belongs to the Console: AddressField sub-labels and placeholders are hardcoded English literals — an address field is untranslatable on any non-English console #4028 family); the gate's Latin-pack blindness is the ruling's own scoping and its header says so; common.search now has zero readers — being filed as a finding for the retire-or-keep decision rather than lost in this thread.

Flipping ready + arming auto-merge.


Generated by Claude Code

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