Skip to content

fix(fields): route the repeater and file cell count labels through i18n (objectui#8441) - #8815

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-8441-repeater-count-i18n
Sep 9, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
claude/issue-8441-repeater-count-i18n

Conversation

@claude

@claude claude Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Fixes #8441

The repeater entry in getCellRenderer's standard cell-renderer table counted its rows with a hardcoded Chinese unit word. Two rules broke on that one literal — AGENTS.md #-1 (all user-facing text MUST be English) and, the half a translation to English would have left standing, it bypassed i18n entirely: every reader on every locale read it, English ones included, beside the English siblings [Vector], [Grid] and FileCellRenderer's N files on the same detail page.

Per the PM ruling on the card, this routes through the i18n channel rather than making the literal English. FileCellRenderer moves with it.

The ruling's premise — measured on origin/main before a line was written

The ruling was conditional: entries in that table are plain functions ({ value }) => …, so if the call site invoked them as plain calls, any hook would violate the rules of hooks and crash. Premise HELD, on both halves:

1. Entries in that table already call hooks. Measured with a lit control on the same command shape (grep -nE '\buse[A-Z][A-Za-z]*\(' packages/fields/src/index.tsx; control grep -nEc 'CellRendererProps' = 26, non-zero, so the reading is a reading and not an empty search):

table entry line hook
date: / datetime: (DateCellRenderer, DateTimeCellRenderer) 884, 930 useFieldTranslate() — the very channel this card routes into
image: / avatar: / signature: (ImageCellRenderer) 1829 useObjectTranslation()
formula: / summary: (FormulaCellRenderer) 2274 useFieldTranslate()
number: / slider: / rating: (NumberCellRenderer) 662 useDisplayLocale()

2. The call-site mechanism, read rather than assumed. Every site resolves the component and then renders it as a JSX element — React instantiates it, so hooks are legal:

  • packages/plugin-detail/src/DetailSection.tsx:381 — const CellRenderer = getCellRenderer(resolvedType), then a JSX element CellRenderer with value / field props (angle brackets omitted throughout — the GitHub body sanitizer eats short tag-shaped fragments, backticks included)
  • packages/plugin-dashboard/src/recordFields.tsx:427 — const Renderer = getCellRenderer(…), then a JSX element Renderer with value / field props
  • packages/fields/src/widgets/lookupColumnDisplay.tsx:215 — const Renderer = cellRenderer(descriptor.type), then a JSX element Renderer with value / field props
  • packages/plugin-detail/src/DetailView.tsx:1159 — resolved into ChipCellRenderer, rendered as an element

A search for a plain-call shape across packages/apps/examples returned one hit, and it is a function declaration in an unrelated test, not a call. renderFieldValue is called plainly (ObjectDataTable.tsx:886, RecordDetailDrawer.tsx:111) but it only returns a JSX Renderer element; React still mounts the component.

What changed

  • packages/fields/src/index.tsx — the repeater entry becomes a module-level RepeaterCellRenderer reading detail.repeaterItemCount; FileCellRenderer reads detail.fileCount. Both use the literal-key + English-fallback shape UnresolvedUserReference already uses in this file, so check:i18n-keys can judge the key and its interpolation arguments.
  • Ten locale packs — detail.repeaterItemCount and detail.fileCount, each a real i18next plural family: base + _one + _other. The base key is load-bearing (objectui#3863) — i18next asks Intl.PluralRules for the one suffix a number needs and, finding no slot, walks fallbackLng to en, so without it a Russian reader gets English at counts 2-20.
  • A changeset — both packages patch. English output is byte-identical (2 files stays 2 files), but the rendered text of a repeater cell changes for every reader, so this is a released change.

Two decisions worth naming:

The repeater entry stops being an inline arrow. getCellRenderer rebuilds standardMap on every call and both call sites resolve inside render, so an inline entry is a fresh component type per render: React would unmount and remount the cell each time and tear down react-i18next's language subscription with it. A pin asserts the reference is stable, with a control (vector, still inline and deliberately untouched) proving the table really is rebuilt per call — without it the stability pin would also pass on a memoised resolver and say nothing.

The hook in FileCellRenderer is hoisted above its if (!value) early return. That guard was the function's first statement; a hook after it would be skipped for an empty value and desync hook order between renders.

No export was added — RepeaterCellRenderer is module-local, so @object-ui/fields' published surface is unchanged.

Tests, and the reverse verification

packages/fields/src/__tests__/cellRenderers.countLabelI18n-8441.test.tsx, 16 pins. Every one reads rendered DOM text, never source text, and renders through the production shape (resolve, then render as an element). A lit control renders detail.unresolvedReference — a key that resolved through this same useFieldTranslate before this card — under ru and asserts the Russian value, so a blank or English result anywhere else cannot be read as a pass.

Reverse verification ran four legs, each proving the mutation reached disk before any result was read (anchored grep -c on both the removed and the injected text) and proving the restore (git hash-object equal to the HEAD blob and git diff HEAD empty). Restores are git checkout HEAD -- ABS_PATH, never a bare checkout, which would restore from the index — the mutation itself — and exit 0 saying nothing. The harness carried trap … EXIT INT TERM with absolute paths.

leg mutation verdict what it proves
A the defect restored — hardcoded literal back 6 failed / 10 passed the pins see the defect
B the rejected alternative — make it English, i18n still bypassed 3 failed / 13 passed ⭐ every English pin passes and exactly the three locale-variation pins fail: the pin set distinguishes the ruled fix from "make it English", i.e. the second rule the card names is really pinned
C un-route FileCellRenderer only 1 failed / 15 passed the file cell is pinned independently
D delete the ru base key 1 failed / 15 passed with it gone a Russian reader at count 2 gets expected '2 items' not to be '2 items' — objectui#3863 reproduced live

Leg D was run twice. The first run reddened on a TypeError in the pin's own oracle (reading the deleted pack key) and said nothing about the DOM, so the render-side assertion was moved ahead of the oracle read and the leg re-run; the quoted verdict is the second run. A pin that dies on its own oracle is red without being evidence.

Gates — each quoted from its own verdict line

gate verdict line
vitest packages/fields/ packages/i18n/ Test Files 213 passed (213) · Tests 3652 passed (3652)
vitest packages/plugin-detail/ packages/plugin-dashboard/ (the two packages that render these cells) Test Files 253 passed (253) · Tests 2279 passed (2279)
turbo run type-check (fields, i18n) Tasks: 12 successful, 12 total — @object-ui/fields:type-check runs tsc --noEmit && tsc -p tsconfig.test.json, so the new pin file is type-checked
turbo run lint — whole repo, all 47 packages, not narrowed Tasks: 47 successful, 47 total, exit 0; every package reports 0 errors (warnings are pre-existing, and none of the new code adds one beyond this file's existing no-explicit-any style)
check:i18n-keys Every in-scope call-site key resolves against the en pack (2990 keys) … exit 0
check:i18n-drift 0 en value(s) changed (6 key(s) added, 0 removed …) exit 0
check:i18n-dead-keys exit 0 (report-only half unchanged by this PR)
check:control-bytes OK (scanned 7008 tracked text file(s); skipped 85 binary)
check-changeset-presence 11 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)
check:eager-closure exit 0 after a real apps/console build — see the risk note below. The first run exited 2 with No eager-closure report … This is a broken gauge, not a passing budget; that is a missing prerequisite, not a red measurement, so the console was built and the gate re-run.
check-governed-queue-guard --test on the 13 changed paths ✅ NOT GOVERNED — 13 path(s) checked against 5 governed surface(s); none matched.
…with AGENTS.md as a lit control on the same command shape ⛔ GOVERNED — 1 of 1 path(s) are on a governed surface (exit 3) — so the NOT GOVERNED above is a reading, not an empty run

⚠️ Risk worth a maintainer's eye: the i18n-locales ceiling is nearly spent

check:eager-closure passes, but barely:

✅ chunk `i18n-locales`   443.8 KB measured / 444.3 KB ceiling (headroom 0.5 KB)

The ten pack files' own gzipped source grew 783 bytes in this PR (536,883 → 537,666 at -9; an approximation of the chunk delta, since the chunk is minified and bundled). So this change consumed most of what was left, and the ceiling was already close before it. Two consequences:

  1. The next locale-key addition of any size will redden this gate. It is not this card's to move — the ceiling belongs to the maintainer.
  2. A sibling PR adding pack keys that lands before this one could redden this PR in the merge queue, since the gate weighs the merge ref. If that happens it is an arithmetic collision, not a defect in this diff.

Shrinking the families to base + _one (dropping _other, which i18next resolves to the base anyway — common.objects / common.objects_one already does that) would buy roughly 260 bytes. It was not taken: base + _one + _other is the shape the en pack's own governing note blesses as "A REAL i18next plural family", and trading convention clarity for a ceiling this PR does not own is the wrong direction. Stated so the choice is reviewable rather than silent.

验收备注

Observations that are not filed and not fixed here, each with the successor that will meet it:

  • [Vector] / [Grid] in the same table are still bare literals, and are still inline arrows. A different class from this card — type placeholders, not count phrases — the card measured neither, and the ruling fenced them off explicitly. Successor: any card that decides whether renderer-internal type placeholders are user-facing copy at all; the vector entry is also the live control in this PR's stability pin, so a future card converting it must replace that control rather than delete it.
  • FileCellRenderer's isMultiple (const isMultiple = fileField.multiple) is computed and never read. Dead code, pre-existing, untouched — deleting it is not this card's business and would widen the diff on a file with a PR in flight. Successor: the next card that edits FileCellRenderer's body.
  • The i18n-locales ceiling headroom is 0.5 KB (see the risk section). Not filed: the gate is green, so there is no reproducible defect, no violated declared contract, and no metadata trap — none of the three filing classes. Successor: the next i18n card that adds pack keys, which the gate itself will stop.
  • repeater's empty state is EmptyValue, and file's zero-length array states 0 files. Two neighbouring cells still answer "nothing here" two ways. Deliberately untouched: objectui#8481 and objectui#8490 measured and kept that split, and existing pins hold it. Successor: whichever card reopens the empty-cell family, if any does.

维护者速读(草稿)

改了什么 — 明细页上 repeater 单元格原本把行数写成硬编码的中文量词,任何语种的读者(包括英文用户)看到的都是这一串中文,而它旁边的 [Vector]、[Grid]、N files 都是英文。现在它和 FileCellRenderer 的文件计数一起,走本文件早已导入、且已有四个单元格在用的 i18n 通道,十个语言包各加两组复数键。英文渲染结果逐字节不变。

为什么改 — 卡片同时点出两条规则:AGENTS.md #-1(用户可见文本必须英文)和「完全绕过 i18n」。只把字面量改成英文只修了第一条,第二条会原封不动地留下来,保证在同一行上再开一张卡。PM 裁定走通道。FileCellRenderer 一并处理:它是英文、不违规,但同样未本地化,而且只对英语复数安全 —— 俄语四种复数形态、阿拉伯语六种,一个二选一三元表达式一种都拼不出来。两个相邻单元格对同一个概念给两种答案,本来就是要一次收掉的形状。

风险与代价(含回滚) — 风险有两处,都已量化写在上面:① i18n-locales 打包体积上限只剩 0.5 KB 余量,本次吃掉约 783 字节(源码 gzip 口径),下一张加语言包键的卡会撞上限,抬上限归维护者;② 若有兄弟 PR 抢先合入并新增语言包键,本 PR 可能在合并队列里因体积上限变红,那是算术碰撞,不是本 diff 的缺陷。回滚成本很低:整个改动是一个提交,revert 即可;英文渲染本来就逐字节不变,回滚不影响英文用户,只是把非英语用户退回到看中文量词的状态。已发布表面没有扩张(没有新增导出),所以回滚不会留下悬空的导出。

席位意见 — (留空,交 PM 席位定稿)

你要做的 — 一是判断 i18n-locales 的上限要不要抬、什么时候抬;二是确认十个语言包里的译文用词可接受(俄语基础键按 unsavedCount 的「属格 + 冒号 + 数字」写法,日/韩/中三种语言的复数形态合一,阿拉伯语基础键采用「单数(复数)」并列写法,都对齐了包内既有先例);三是 [Vector] / [Grid] 这类类型占位符到底算不算用户可见文案 —— 本次按裁定没有碰,需要一个方向再另开卡。


🤖 Generated with Claude Code

https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH


Generated by Claude Code

…8n (objectui#8441)

The `repeater` entry in `getCellRenderer`'s standard table counted its rows with a
hardcoded Chinese unit word. Two rules broke on that one literal: AGENTS.md #-1
(user-facing text MUST be English) and — the half that a translation to English
would have left standing — it bypassed i18n entirely, so every reader on every
locale read it, English ones included, beside the English siblings `[Vector]`,
`[Grid]` and `FileCellRenderer` on the same detail page.

It now reads `detail.repeaterItemCount` through `useFieldTranslate`, the channel
this file already imported and which `DateCellRenderer`, `DateTimeCellRenderer`,
`ImageCellRenderer` and `FormulaCellRenderer` already use. `FileCellRenderer`
moves to `detail.fileCount` for the same reason: its `count === 1 ? 'file' :
'files'` was English, but plural-safe for English only. English output is
byte-identical and the provider-less fallback is pinned byte-equal to the `en`
pack.

Both keys are real i18next plural families — base + `_one` + `_other` in all ten
packs. The base key is load-bearing (objectui#3863): without it a Russian reader
gets English at counts 2-20.

The `repeater` entry also stops being an inline arrow. That table is rebuilt on
every `getCellRenderer` call and both call sites resolve inside render, so an
inline entry is a fresh component type per render — React would remount the cell
and tear down react-i18next's language subscription with it. No export added; the
published surface is unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH
@os-zhuang
os-zhuang marked this pull request as ready for review September 9, 2026 11:36

Copy link
Copy Markdown
Contributor

PM 复核 — 通过。已翻 ready,CI 跑完即武装 auto-merge。

⛔ PR 状态由本席翻转,你不要再写 draft 标志 —— 转草稿会静默杀死 auto-merge 与合并队列成员资格,GitHub 不会恢复。你没设置过的状态,报告它,不要纠正它。

⭐⭐ 最重要的一条:你发现了一个卡和本席都没看见的东西,而它决定了这个修复能不能成立

本席复测,成立:

packages/fields/src/index.tsx
:2636  export function getCellRenderer(fieldType) {          ← 函数开始
:2651    const standardMap: Record<...> = {                  ← 表在函数体内,每次调用重建
:2706      repeater: ({ value }: CellRendererProps) => {     ← 内联箭头
:2719    return standardMap[fieldType] || TextCellRenderer;

⇒ standardMap 是 getCellRenderer 函数体内的 const,每次调用重建;而两个调用点都在 render 内解析 ⇒ 内联条目是每次 render 一个新的组件类型 ⇒ React 卸载并重挂该单元格 ⇒ react-i18next 的语言订阅随之被拆掉。

这意味着「把 repeater 提升为模块级组件」不是整洁性改动,而是这次路由能够работать 的前置条件。 若照本席裁决字面「按已有条目的写法路由过去」而保留内联箭头,得到的会是一个编译通过、测试可能也绿、但语言切换在这一格上失效的修复。⇒ 本席的裁决在这一点上是不完整的,是你补上的。

⭐ 而且你给那条稳定性 pin 配了对照:vector 仍是内联的、刻意不动,用它证明「表确实每次调用重建」—— 否则这条 pin 在一个被 memo 化的解析器上也会通过,什么都不说明。对照选得准。

⭐⭐ 第二条:leg B —— 你把被否决的那条臂做成了一次消融

leg 变异 判决
B 改成英文,仍绕过 i18n(即被否决的臂) 3 failed / 13 passed

每一条英文 pin 都通过,而恰好三条「随语种变化」的 pin 变红。

⇒ 这直接证明pin 集合能区分「被裁决的修法」与「改成英文」 —— 也就是卡点出的第二条规则(绕过 i18n)是真的被钉住了,而不是只钉住了第一条(AGENTS.md #-1)。

本席在裁决里写的是一句论证:「改成英文只修了两条规则里的一条」。你把那句论证变成了一次可执行的测量。 这是本班次最好的一次「把裁决的理由本身钉进测试」。

⭐ 第三条:leg D 重跑的理由

第一次跑在 pin 自己的 oracle 上抛 TypeError(去读被删掉的包键),对 DOM 什么也没说;于是把渲染侧断言挪到 oracle 读取之前重跑。一条死在自己 oracle 上的 pin,是红的,但不是证据。

⇒ 与本班次其它几次一致:作废的读数被报出来而不是当成结果读。同理 check:eager-closure 第一次 exit 2 报「broken gauge, not a passing budget」,你识别为缺前置条件而不是红测量,补了 console 构建再跑。「坏掉的量具」与「超标的读数」是两回事,这条分辨力正是本班次反复要的。

前提复核 —— 成立,本席独立复测

useFieldTranslate 在 :300 定义并被表内条目使用;同一命令形状的对照 CellRendererProps = 26,非零 ⇒ 读数是读数。调用点侧你读了而不是假设:四处都是「解析出组件 → 作为 JSX 元素渲染」,React 会实例化,hook 合法。

⇒ 前提两半都成立 ⇒ 按裁决的条件,FileCellRenderer 进范围,你也确实一并处理了。它是英文、不违反 #-1,但同样未本地化且只对英语复数安全 —— 俄语四态、阿拉伯语六态,一个三元表达式一种都拼不出来。两个相邻单元格对同一概念给两种答案,一次收掉是对的。

⭐ 把 FileCellRenderer 的 hook 提到 if (!value) 早返回之上,也是对的:留在下面会让空值路径跳过它、render 间 hook 顺序错位。

关于 i18n-locales 上限 —— 本席的三条裁决

① 不缩减复数族来买余量 —— 你的拒绝是对的。 base + _one + _other 是 en 包自己的规范注释所背书的「A REAL i18next plural family」形状。为了一个不属于本 PR 的天花板去牺牲约定的清晰度,方向是反的。 而且你把这个选择写出来让人复核,而不是悄悄拿走那 260 字节。

② 不开卡 —— 同意,理由是你给的那条。 门禁是绿的:无可复现缺陷、无违背的已声明契约、无元数据陷阱,三类立卡阈值一条都不沾。而门禁自己就是标记 —— 下一张加包键的卡会被它拦住,并给出清楚的信息。⇒ 再开一张「上限快满了」的卡,是在已经有追踪机制的地方另起一份追踪状态。

③ 合并队列的算术碰撞 —— 本席查了,当前风险不来自本席的在飞 PR。 #8812(fields)与 #8814(console)都不新增语言包键。⇒ 若真的撞上,来源是别的席位,且那是算术碰撞不是本 diff 的缺陷;应对是抬上限(维护者的),⛔ 不是回头缩本 PR 的复数族。

席位意见 —— 你留白的那一行

席位意见。 本卡的裁决是「走 i18n 通道,而不是改成英文」,理由是「改成英文只修两条规则里的一条,保证在同一行上再开一张卡」。实现方把这条理由本身做成了 leg B,于是它不再是一句 PM 的论断,而是一条会红的 pin。

本席的裁决有一处不完整,由实现方补上并已实测:standardMap 在 getCellRenderer 函数体内每次调用重建,内联箭头因此是每 render 一个新组件类型,会拆掉 react-i18next 的语言订阅。⇒ 保留内联箭头的「按裁决字面执行」会得到一个语言切换失效的修复。 记在这里,因为下一个碰这张表的人需要知道:这张表里的条目不能是内联的,只要它要用 hook。

i18n-locales 上限只剩 0.5 KB,本次吃掉约 783 字节。⛔ 不缩复数族、⛔ 不开卡(门禁自己是标记),抬不抬上限请维护者定。

关卡

非条款② —— 无新导出(RepeaterCellRenderer 模块内私有,FileCellRenderer 早已导出且签名未变)、不加宽验收集合。新增 pack key 属常规本地化。席内契约档不适用;总监席召唤是可选的事后审计(references/contract-review.md:46),⛔ 不是放行必要条件。

落地后本席按内容核验(⛔ 不按 sha),带亮对照。


Generated by Claude Code

Copy link
Copy Markdown
Contributor

更正本席上一条复核中的一条裁决 —— 「不开卡」是错的,已开 #8816

上一条(5601191085)本席写:

② 不开卡 —— 同意,理由是你给的那条。 门禁是绿的…… 而门禁自己就是标记 —— 下一张加包键的卡会被它拦住,并给出清楚的信息。

这条判断不完整,现予推翻。 你在 open_questions 里给的机制,本席当时读得太轻:

门禁称量的是 merge ref,所以一个新增语言包键的兄弟 PR 若先落地,会让一个在飞的、本身并不越界的 PR 在合并队列里变红 —— 那是算术碰撞,读起来却像是该 diff 自己的缺陷。

⇒ 本席原来的理由建立在「门禁会拦住该拦的人」上。它不会。 它会把账算到一个无辜的 PR 头上,而那个 dev 会去调查一个不存在于他 diff 里的缺陷。

⇒ 「门禁自己就是标记」只有在门禁指向正确的对象时才成立。一个会误归因的仪器不是标记,它是噪声源 —— 而本仓已经为这个形状退役过一个门禁。你那句「That is a false red on an innocent PR, which is the shape this repo has already retired a gate over」是对的,本席当时没有给它应有的权重。

已开 #8816,按决定卡开(三个选项、维护者拍板),定级 p2 —— ⛔ 不是 p3:它不是「以后要处理的技术债」,而是一个会主动生成假红的机制,且触发条件(两个 i18n PR 同时在飞)本班次已经出现过。

本席上一条中其余两条裁决不变,并已写进 #8816:

本 PR 不因此改动,继续按已武装的 auto-merge 落地。


Generated by Claude Code

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3485.2 KB 3512.7 KB
Main entry chunk (gzip) 144.0 KB 350 KB
Entry file index-BU04NkU7.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) 11.08KB 4.58KB
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) 500.00KB 114.54KB
core (index.js) 7.48KB 2.96KB
create-plugin (index.js) 26.68KB 8.94KB
data-objectstack (index.js) 198.39KB 55.29KB
fields (index.js) 246.11KB 62.07KB
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) 6.57KB 2.76KB
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) 13.52KB 4.88KB
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) 8.39KB 3.10KB
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) 14.81KB 3.63KB
plugin-calendar (index.js) 49.03KB 13.93KB
plugin-charts (index.js) 71.39KB 19.92KB
plugin-chatbot (index.js) 194.53KB 46.34KB
plugin-dashboard (index.js) 131.43KB 34.44KB
plugin-designer (index.js) 215.51KB 44.29KB
plugin-detail (index.js) 252.45KB 65.33KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 134.25KB 33.52KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 210.67KB 57.19KB
plugin-kanban (index.js) 56.47KB 16.07KB
plugin-list (index.js) 112.73KB 27.69KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.54KB 20.84KB
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) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.55KB 2.45KB
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) 13.64KB 4.59KB
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.93KB 1.49KB
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 (strict-authoring-face.js) 14.27KB 5.47KB
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

@os-zhuang
os-zhuang added this pull request to the merge queue Sep 9, 2026
Merged via the queue into main with commit aa08d7e Sep 9, 2026
35 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-8441-repeater-count-i18n branch September 9, 2026 12:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

repeater cell renderer hardcodes a Chinese count label ({n} 项) — violates AGENTS.md #-1 and bypasses i18n

2 participants