Skip to content

fix(plugin-chatbot): localize the build-progress panel's own copy (objectui#7388) - #8888

Merged
os-zhuang merged 4 commits into
mainfrom
claude/issue-7388-chatbot-progress-phase-i18n
Sep 10, 2026
Merged

fix(plugin-chatbot): localize the build-progress panel's own copy (objectui#7388)#8888
os-zhuang merged 4 commits into
mainfrom
claude/issue-7388-chatbot-progress-phase-i18n

Conversation

@claude

@claude claude Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Part of #7388 — the first of the two independent pieces the card itself names. The second one (verify phase) is stopped on its precondition and is NOT in this PR; the reasoning and the evidence are in 验收备注 below. Part of, deliberately not a closing keyword, so merging this cannot close a card that still has an open half.

What was wrong

The build panel is half localized, and the half it was missing is the half it owns.

Everything the panel is handed already goes through the console's t(): openBuiltAppLabel, designBuiltAppLabel, previewDraftLabel and the three connection cues are all passed in as translated props by AiChatPage. Everything the panel owned was a literal in the component. So a fully Chinese conversation watched its app get built under:

  • Building your app… / Built your app — the header, both frames;
  • adding sample data — the data-phase suffix;
  • Objects / Views / Dashboards / App / Sample data — the per-type row headings, one per row, on every build;
  • +N more — the overflow counter.

The card names the first two groups. The row headings are the same defect and the more visible instance of it, so this PR is scoped to the predicate — no English literal on the panel bypasses the pack — rather than to a key count.

What changed

  • packages/plugin-chatbot/src/ChatbotEnhanced.tsxBuildProgressPanel resolves all ten strings through useObjectTranslation(), the same channel useMetadataCountBits in this file already uses for the interpolated plan-count strip.
  • BUILD_GROUP_LABEL's English Record becomes useBuildGroupLabel(), a static switch. A computed key (t('chatbot.build.group.' + type)) would be invisible to check-i18n-call-site-keys, which is the gate that proves each key exists in en. Its default arm keeps the old ?? type behaviour verbatim.
  • packages/i18n/src/locales/*.ts — a new chatbot.build.* family in all ten packs.
  • useBuildGroupLabel is module-local; no new exported symbol.

Two shapes worth calling out because they are the ones that could go wrong later:

  1. The unnamed-build stand-in is itself translated (chatbot.build.appFallback) and interpolated into the two header frames, so each frame has exactly one hole — which is what the interpolation-parity half of check:i18n-keys checks. The consequence for translators is a real constraint, and it is written into the en pack's own map-of-contents comment: both frames must keep that noun phrase in one case/gender. ru, fr, es and pt therefore use the deverbal-noun construction in both frames (Создание {{app}}… / Создание {{app}} завершено), which is case- and gender-safe for a real app label and for the stand-in alike.
  2. No fallback was tightened. An unknown artifact type still renders its raw type, never a raw i18n key.

Verification

All readings from the repo root on 5d50a3d0a, after pnpm --filter '@object-ui/plugin-chatbot...' --filter '@object-ui/i18n...' run build.

what command result
new pin pnpm exec vitest run packages/plugin-chatbot/src/__tests__/buildPanelI18n-7388.test.tsx exit 0 — Test Files 1 passed (1), Tests 12 passed (12)
affected packages pnpm exec vitest run packages/plugin-chatbot/ packages/i18n/ exit 0 — Test Files 107 passed (107), Tests 1592 passed (1592)
typecheck pnpm --filter @object-ui/plugin-chatbot --filter @object-ui/i18n run type-check exit 0 (both tsc --noEmit and tsc -p tsconfig.test.json, so the pin is type-checked too)
check:i18n-keys exit 0 — "every in-scope call-site key resolves against the en pack (3001 keys), every literal inline defaultValue matches the value the pack serves, every call site passes exactly the arguments that value has holes for"
check:i18n-drift exit 0 — "0 en value(s) changed (10 key(s) added, 0 removed)"
check:i18n-dead-keys exit 0
check:i18n-designer-parity exit 0
check:changeset-presence exit 0 — "12 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)"
check:control-bytes exit 0
check:governed-queue-guard --test NOT GOVERNED — 13 path(s) checked against 5 governed surface(s); none matched
eslint pnpm exec eslint . --no-inline-config --format json full population, not a narrowing: 4674 files; 0 errors and 0 new warnings in the 13 files this PR touches (the 10 warnings on ChatbotEnhanced.tsx sit at :1844, :1915 and :3778 — all pre-existing, none on a line this PR moved)

check:readme-exports reads NOT MEASURED, not red: it self-declares the population COLLAPSED -- this run proves nothing because it needs the whole workspace built, and it names 26 other packages' READMEs. Neither plugin-chatbot nor i18n appears in its findings once those two are built. Declared to CI, which builds everything.

The control, and that it moves independently

buildPanelI18n-7388.test.tsx runs the three known phases (structure / data / done) twice: through an en provider, pinned to the exact strings the component used to hard-code, and through a zh provider. The provider-less English world is pinned separately and was already there — ChatbotEnhanced.test.tsx's "renders a live build tree" and "collapses to a Built summary", both untouched and green in the 1592.

Ablation, from the committed tree

Restoring the hard-coded Building ${appLabel ?? 'your app'}… literal on top of 5d50a3d0a:

HEAD blob      : b35dead95899bb524bea66c92299995123fe92a8
post blob      : c46cee3add2c91758aa846b476e9d950b029c63d
pre  count NEW : 1   post count NEW : 0
pre  count OLD : 0   post count OLD : 1
ON-DISK PROOF  : blob moved, marker counts inverted

The pin reddens by test-case name, and only on the subject:

× localizes the in-flight header instead of hard-coding "Building X…"
× localizes the unnamed-build stand-in, so the header is Chinese end to end
Tests  2 failed | 10 passed (12)

All four control: a known phase renders exactly as it did before (en) cases are in the 10 that passed — the ablated code renders Building CRM… in English too, which is exactly why the control does not move when the subject breaks.

Restored by state, not by exit code: git hash-object back to b35dead9… and git diff HEAD empty.

⚠️ The first ablation attempt was void, not a reading: it passed --reporter=basic, which vitest 4 removed, so the run died in _createServer before entering any test body — the same class as a MODULE_NOT_FOUND exit 1. It was redone with the default reporter; the numbers above are from that run.

验收备注

1. ⛔ 第 2 块(verify phase)停手 —— 前置不成立

按派发词的三步顺序找发送端,三步全空:

  1. 共享契约里没有。 已发布的 @objectstack/spec@17.4.0(本分支 lockfile 的 pin,按已发布 release 判、不看上游 main)对这条通道只声明到 onProgress?: (part: { type: string; id?: string; data?: unknown }) —— dataunknown,整个 phase 词表根本不在契约里。全包 .d.tsverify 只以 verify_build(工具名)和 verifyEmail 一类无关 token 出现。
  2. 本仓没有别的痕迹。 全仓搜不到 phase: 'verify'、搜不到 cloud#1838 的引用;verify_build 在本仓确实存在,但它走的是工具卡通道和 BuildDebugDrawer,不是 data-build-progress 的 phase 通道。
  3. ⇒ 按裁决第 3 条:停手回报,第 1 块单独交付。

另有一条比"找不到证据"更强的发现,请 PM 转给 cloud 侧: data-build-progressapply_blueprint 通过自己的 ctx.onProgress 发的。而卡片自己实测的场景是「apply_blueprint 返回之后又花了 111 秒做校验」—— 工具已经返回,它不可能再往这条通道上发帧。所以即便 cloud 愿意发 verify,「在 extractBuildProgress 里加一个 verify phase」这个字面方案也盖不住实测的那 111 秒:要么换一个发送方(校验循环自己发一条新的 data-build-progress 帧),要么这个 phase 得挂到另一条通道上。⚠️ 这是发送端的设计决定,在本会话仓库范围内答不了 —— 建议第 2 块回到 cloud#1838 一侧先定发送方,再回头开 objectui 的接收端卡。

2. 相邻的第二处同形兜底 —— 只推理,⛔ 未改其行为

extractBlueprintProgressconst phase: ChatBlueprintProgress['phase'] = d.phase === 'done' ? 'done' : 'designing';extractBuildProgressd.phase === 'data' || d.phase === 'done' ? d.phase : 'structure' 是同一形状。按边界要求未改动,推理如下:

两者的风险并不对称,所以「都该 warn 一次」这个直觉我认为对一半:

  • extractBuildProgress 的兜底把未知值折叠成 'structure',那是一个中间状态 —— 面板会继续转圈、继续说"正在构建"。折错了就是卡片描述的那种 actively wrong
  • extractBlueprintProgress 的兜底折叠成 'designing',同样是中间状态,但它的注释已经把这条写成契约而不是兜底:Only 'done' is authoritative; anything else (incl. absent) is still designing. 并且这个面板一旦 propose_blueprint 的结果落地就被权威的 "Proposed plan" 卡取代,所以一次误折的暴露窗口是有界的。

⇒ 我的意见:真要加 warn,先加在 extractBuildProgress,而且应当和第 2 块同一个 PR 落地(词表和"未知值"的定义必须一起改 —— 先加 warn 再加词表,会让 verify 在落地前的每一帧都刷一条假告警)。⛔ 因此本 PR 两处都没动。

3. ⚠️ 卡片的另一半确实过时了,而且比分诊席测到的更彻底

分诊席说 humanizeToolName 已有翻译接缝。按派发词要求实测后确认,并且接缝今天是通的、表也是满的:

  • 两个调用点都传了活的 translator:ChatbotEnhanced.tsxhumanizeToolName(tool.toolName, tt),以及 summarizeTools(summaryTools, tt)translateToolTitle 传下去的那个。
  • 翻译表不缺:chatbot.tool.*enzh35 个键、完全对齐,含 verify_build

⇒ 卡片那句 "a column of English tool names ... Nothing in the tool result or the stream can change either" 今天对平台工具完全不成立,连"表不全"这个退路都不成立。这一半无事可做,本 PR 未碰。第三方 / 自定义工具仍然降级到英文 title-caser,那是 objectui#7254 的既定设计(toolCardI18n-7254.test.tsx 有一条用例正是钉这个)。

4. 范围内但未立卡的观察(noted, not filed)

  • LemptyTitle / emptyDescription / clear / sendHint / copy / copied / regenerate / model / submit / uploadFiles / stopResponse / trace / viewTrace / toolDetailsHidden / sendFailedRateLimited / sendFailedGeneric 这 16 条走的是 labels?.x ?? '英文字面量',没有 tt() 那一层;AiChatPage 只喂了其中一部分。也就是说:一个不喂 labels 的宿主拿到的是英文,而同一个 L 里 objectui#7254 加的那几条(toolCompleted 等)已经有 tt() 兜底。这是同一类问题在同一个对象里的两种写法并存,但它在聊天外壳上、不在本卡的面板上,⛔ 未纳入本 PR。承接者:无 —— 没有已排队的 PR 或人会碰到这个对象,所以我没有替它立卡,交 PM 判是否值得单开一张。

⚠️ Bundle Analysis: the shared i18n-locales budget, re-baselined (objectui#8816)

This PR and objectui#8888 / objectui#8901 (the other one) were red on one and the same
check
, for one and the same reason, and neither could go green alone. That is one
shared-budget problem wearing two PR numbers, so it was taken as one dispatch and the answer
is identical bytes on both branches — required, not tidiness: see "Why both branches
carry it" below.

Route taken: (1) raise the ceiling. Route (2) — reduce the payload — was measured first
and cannot reach the gap. The reasoning, the arithmetic and what the raise costs are written
into scripts/check-eager-closure-budget.mjs itself, under "Why i18n-locales moved UP",
because that is where the budget lives and where the next author will look.

The instrument

apps/console/dist/eager-closure.json, written by emitEagerClosureReport in
apps/console/vite.config.ts on every console build, read by
node scripts/check-eager-closure-budget.mjs (pnpm check:eager-closure). Every figure below
is one full build pair from the repo root — pnpm turbo run build --filter='./packages/*'
then pnpm --filter @object-ui/console build — in one container. ⛔ No figure here is
inherited from an earlier seat's comment; the prior readings on objectui#8816 were all
re-derived from scratch, and where they agree that is two independent paths landing on the
same number, not a citation.

Ceiling, base, and each branch's delta — measured

tree i18n-locales gzip vs the old 455,000 ceiling
bbe285ee7main 454,602 headroom 398
3949cf3a3main + objectui#8901 455,271 OVER by 271 (+669)
ea5eab7b3main + objectui#8888 455,519 OVER by 519 (+917)
ba20b0bc0main + BOTH 456,196 OVER by 1,196 (+1,594)

⭐ The fourth row is the one this dispatch existed to produce. The two deltas sum to 1,586
against a measured 1,594, so two independent claimants on this chunk are additive to within
8 bytes
— no gzip dictionary relief, and no arithmetic on the two single readings would have
been trustworthy without that third build.

main then advanced five commits mid-flight (bbe285ee7b97129e96). Both branches were
merged onto it again and everything re-measured: packages/i18n is untouched in that range
and i18n-locales came back byte-identical on all three trees (455,271 / 455,519 /
456,196). The other three budgeted chunks moved by ≤9 bytes, which is main's own movement,
not this change's.

The new pair, and why the number is what it is

PER_CHUNK_GZIP_CEILINGS['i18n-locales']  455,000 -> 465,000
PER_CHUNK_BASELINE['i18n-locales']       446,076 -> 456,196   (measured on ba20b0bc0)

Headroom 8,804 bytes = 0.10x REGRESSION_THIS_GATE_MUST_CATCH_BYTES — this key's own
convention, and marginally tighter in ratio than the 8,924 (0.10x) the retired pair
carried.

⛔ It is deliberately not the 804 bytes the overage needed. A minimal raise reproduces the
exact defect objectui#8816 was filed about: the gate weighs the merge ref, so a line with
~0 headroom reddens whichever in-flight PR happens to be weighed second and prints a message
blaming that diff for arithmetic that is not its own. objectui#8554 is the same mechanism
one step earlier (framework at 70,999 against 71,000, printing a green sensitivity row).

⚠️ And it is not a settlement. Measured runway: the retired pair landed on 177afeba1
(2026-09-03) at 446,076 and main measured 454,602 on 2026-09-10 — 8,526 bytes in seven days,
the last 398 of them claimed by two independent changes inside one shift. At that arrival rate
this buys about a week. The structural answer the file already names — taking the catalogues
out of the eager closure — is what retires this line instead of moving it.

Why route (2) was refused, measured rather than assumed

  • objectui#8888's ten keys include five generic console nouns (Objects, Views,
    Dashboards, App, Sample data), so reuse looks available. It is not: only Objects and
    Dashboards have any pre-existing en equivalent, and each already exists three times
    under three per-surface namespaces. Per-surface keys are this pack's convention; cross-surface
    reuse is the deviation. Best measured saving 128 bytes against a 519-byte overage.
    ⭐ The probe is control-lit: searching the en pack for the five literal values returned
    four hits for two of them and one (the new key itself) for the other three — so the three
    zeros are real zeros, not a broken search.
  • objectui#8901's three keys have no reuse candidate, and that is structural: those
    strings exist precisely because saying what the neighbouring console.importMappings*
    strings say is the runtime lie that card was filed to remove.
  • Shortening the copy is the objectui#6759 lever and is ⛔ refused. Widening a ceiling to get a
    green tick and narrowing a payload to get one are the same error facing opposite ways.

⇒ ⭐ The payload reduction that would make the next raise unnecessary is real, and it is not
this PR's to take.
pnpm check:i18n-dead-keys reports 364 candidates across 47 namespaces,
127 of them CONFIRMED
with no textual footprint anywhere in the repo, in ten packs each —
far more than the 1,196 bytes needed. That gate is report-only by design, @object-ui/i18n
publishes these packs (so removing a key is a published-surface decision, not a byte
saving), and objectui#8816's own route-C note says a sweep like that needs its own card and its
own reverse verification. ⛔ Not ridden in here.

Proof the gate can still go RED at 465,000

A ceiling that no longer fires is not a fix, so it was made to fire. On the combined tree
ba20b0bc0, with the new 465,000 ceiling in place, 300 deterministic high-entropy filler
keys (30 per pack, ten packs) were injected, proven on disk before anything was read —
per-file marker count 0 → 30 and a changed blob hash for all ten — then the console was
rebuilt:

i18n-locales   456,196 -> 471,668   (+15,472)
❌ i18n-locales   460.6 KB / 454.1 KB ceiling (OVER by 6.5 KB)   check:eager-closure exit 1

Direction was predicted before the run and observed as predicted. The lit controls held:
vendor-objectstack and ui-components byte-identical across the mutation, framework moved
2 bytes, and the aggregate stayed green (7.9 KB headroom) — so the line that objected is
the per-chunk one, at its new number.

Restored by git checkout HEAD -- PATH (⛔ never bare git checkout --) and verified by
state
, not by an exit code: all ten blobs back to their HEAD hashes, marker count 0,
git diff HEAD empty for the locale packs, and the mutation script carried
trap … EXIT INT TERM with absolute paths throughout. The dist/ the mutated build wrote was
also deleted, so no later run in that container can read the mutated bytes back.

The green side, and the both-on-main answer

tree verdict i18n-locales
objectui#8901's merge result check:eager-closure exit 0, all four chunks ✅ 455,271 / 465,000 (headroom 9,729)
objectui#8888's merge result check:eager-closure exit 0, all four chunks ✅ 455,519 / 465,000 (headroom 9,481)
both on main check:eager-closure exit 0, all four chunks ✅ 456,196 / 465,000 (headroom 8,804)

Yes, the fix holds with both on main, and that is a build, not an extrapolation. The
aggregate never objected in any of them: 23,507 bytes of headroom (0.26x) with both claimants
on it, so MAX_EAGER_CLOSURE_GZIP_BYTES is ⛔ untouched.

Why both branches carry the identical change

Not duplication — evaluateCeilingFreshness requires it. Once the first of the two lands, the
base branch has moved PER_CHUNK_GZIP_CEILINGS, and the freshness half compares three
readings: this checkout, this checkout's base, and the base-branch tip. A checkout that does
not carry the new value scores as superseded and exits 2 — a verdict about the gauge,
correctly. Because both branches carry byte-identical constants, the second one merges the
first's landing as a no-op for that file and the freshness half reports "this checkout already
carries the new value". So whichever lands first, the other stays green.

The measurement is payload-neutral, and that was checked rather than argued

The ceiling edit plus the ten locale packs' comment update produced a byte-identical
closure on every tree it was applied to — i18n-locales 455,271 → 455,271, 455,519 → 455,519,
456,196 → 456,196, aggregate unchanged to the byte in all three. So BASELINE's standing
argument (scripts/check-*.mjs is not a console build input) is observed here rather than
assumed, and it is now also observed for a source edit: TypeScript comments do not survive
into the chunk.

Also in this change: ten stale citations of the retired headroom

All ten locale packs carried a comment naming the old ceiling's headroom ("8,924 B … about
sixty short keys' worth"). Moving the ceiling would have left ten copies of a false number, and
the "about sixty short keys" unit is itself falsified by measurement — the two real claimants
cost 9.2 and 22.3 gzipped bytes per key-times-locale, a 2.4x spread, so ⛔ no quota
written in keys is derivable. The comment now names no figure at all (the form that file's
own anti-drift rule explicitly allows) and points at pnpm check:eager-closure for the figure
in force. Comment-only: zero payload, proven above.

⛔ What was NOT done

No check disabled, skipped or removed. No package excluded from Bundle Analysis. No test
deleted. No empty commit. Neither PR closed and reopened. REGRESSION_THIS_GATE_MUST_CATCH_BYTES
untouched, the other three per-chunk ceilings untouched, MAX_EAGER_CLOSURE_GZIP_BYTES
untouched. No assignee written, no review label hung or cleared, no Clause-② declaration
edited. No rebase and no force-push: main was brought in with real merge commits, twice.

One commit on this branch was not this session's

While the final measurement build was running, another actor pushed a Merge branch 'main'
commit onto this branch — 7b01b45c5, committed by GitHub, i.e. the "Update branch" path. It
has the same two parents and a byte-identical tree (8794b5848) as the merge this session
had already made locally, so nothing was lost and nothing needed redoing. That commit was
adopted, ⛔ not reverted and ⛔ not force-pushed over, and the green reading above is a
measurement of that exact tree.

🤖 Generated with Claude Code — session session_01611D6ZaRaMmwTNQmSbk8MH


Generated by Claude Code

…jectui#7388)

Every label the build panel is HANDED was already localized: the console host
passes `openBuiltAppLabel`, `designBuiltAppLabel`, `previewDraftLabel` and the
three connection cues through its own `t()`. Every string the panel OWNED was
a literal in the component, so a fully Chinese conversation watched its app get
built under `Building your app…`, over `Objects` / `Views` / `Dashboards` /
`App` / `Sample data` row headings — one per row, on every build — and a
`+N more` overflow counter.

All of them now resolve through the console's pack as `chatbot.build.*`,
added to all ten locales. `BUILD_GROUP_LABEL`'s English `Record` becomes a
static `useBuildGroupLabel()` switch: a computed key would be invisible to
`check-i18n-call-site-keys`, and its `default` arm keeps the old `?? type`
behaviour verbatim, so an unknown artifact type still renders its raw type and
never a raw i18n key.

Known-phase rendering in English is unchanged, pinned in both worlds by
`buildPanelI18n-7388.test.tsx` plus the pre-existing provider-less pins in
`ChatbotEnhanced.test.tsx`.

Part of #7388 — the `verify` phase half is NOT here: nothing in the published
`@objectstack/spec@17.4.0` (nor anywhere in objectui) declares or emits a
`verify` phase on `data-build-progress`, so admitting one would build a
receiver with no producer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH
@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) 3488.4 KB 3512.7 KB
Main entry chunk (gzip) 144.1 KB 350 KB
Entry file index-DNHQ4Ah-.js
Status FAIL

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.

Which half objected:

Eager-closure half Verdict
Aggregate closure ceiling ✅ pass
Per-chunk ceilings ❌ over its ceiling
Ceiling sensitivity (headroom) ✅ pass
Ceiling freshness (checkout vs. base branch) ✅ pass

📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.69KB 6.21KB
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.23KB 114.67KB
core (index.js) 7.48KB 2.96KB
create-plugin (index.js) 26.68KB 8.94KB
data-objectstack (index.js) 200.01KB 55.77KB
fields (index.js) 246.94KB 62.28KB
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) 195.32KB 46.51KB
plugin-dashboard (index.js) 131.50KB 34.45KB
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) 136.26KB 34.13KB
plugin-gantt (index.js) 166.96KB 40.93KB
plugin-grid (index.js) 210.86KB 57.28KB
plugin-kanban (index.js) 57.53KB 16.46KB
plugin-list (index.js) 112.54KB 27.65KB
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.42KB 20.80KB
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

Copy link
Copy Markdown
Contributor

⚠️ Bundle Analysis 红 —— 撞上 objectui#8816 那 0.5 KB,⛔ 本 PR 不自行抬天花板

失败的是 Bundle Analysis(job 102618524688,head 5d50a3d0a)。逐字读数:

❌ 1 eager chunk is over its per-chunk budget:
  ✅ vendor-objectstack      1207.3 KB / 1224.6 KB ceiling (headroom 17.3 KB)
  ❌ i18n-locales             444.8 KB / 444.3 KB ceiling (OVER by 0.5 KB)
  ✅ ui-components            385.2 KB / 389.6 KB ceiling (headroom  4.5 KB)
  ✅ framework                 71.7 KB /  97.7 KB ceiling (headroom 26.0 KB)

⛔ 先排除「不是本 PR 的锅」这条路 —— 排不掉

⇒ ⛔ 不是基线漂移、⛔ 不是 flake、⛔ 不是 base 分支红。这 0.5 KB 是本 PR 的:10 个 chatbot.build.* 键 × 10 个 locale 包,顶过了 i18n-locales 这一个 chunk 自己的天花板。⚠️ 注意它藏在聚合天花板 24.3 KB 的余量里 —— 门禁的注释原话:「one chunk growing while others shrink is invisible to a single total」(objectui#5490)。这次它按设计工作了。

⛔ 为什么本 PR 抬那个天花板

门禁自己给的出口是「若增长是有意的,就审慎地PER_CHUNK_GZIP_CEILINGS 里那一项、同步移动 PER_CHUNK_BASELINE、并在 PR 里说明这些字节买到了什么 —— do not widen it just to get a green check」。

本席裁定:⛔ 不走这条出口,理由不是保守:

抬上限 / 按预算配给 / 拆 chunk,这三件事正好是 objectui#8816 正在问的那道题,而那是一张 [decision] 卡、p2、等人裁,派发席都不许自裁。

⚠️ 而且 #8816 的标题里那个「只剩 0.5 KB」,就是本 PR 现在超掉的这 0.5 KB。 在这里抬一下,等于让第一个撞上它的 PR 替所有人把答案选成「抬上限」,且不留裁决痕迹。⇒ 本 PR 成为那张卡第一个具体的、被卡住的索赔人,这条事实已同笔记到 #8816 上。

现在在做什么

执行席已收到一轮有界的测量任务(⛔ 不是修复):新增的 10 个键里有 5 个是通用管理台名词(Objects / Views / Dashboards / App / Sample data),去查 en 包里是否已有语义等价的键可复用 —— 若有,这 5 个键 × 10 locale 的字节归零,剩下 5 个大概率进得了坑。
⛔ 复用的判据是语义同一(「将来有人为那个用途改它的值,对我这个用途也应该跟着改」),⛔ 绝不因为英文字符串碰巧一样就复用 —— 那会造出一个别人改一个字、构建面板跟着变而无人变红的耦合。拿不准就不复用,如实报告省不下来。

⚠️ 这个红不额外拖延任何东西

本 PR 本来就停靠着needs:contract-review 已挂(Clause-②: yes),而契约评审档位连续 17 次 429(最近 req_011CetSZe1NTpkMA1o3URsEF,19:47Z)。⇒ 即便现在全绿也进不了队。

⇒ 若测量结论是「省不下来」,本 PR 的正确终局就是停在 #8816 的裁决后面,把阻塞点写明。⛔ 不为绿而抬上限、⛔ 不为绿而拆 chunk、⛔ 不 skip/disable 任何检查、⛔ 不空提交踢 CI。

顺带一条读数,留给 #8816 的裁决者

同一次运行里 ui-components 的余量是 4.5 KB(= 门禁要抓的那个 89.0 KB 回归的 0.05x),被判 ✅ 但已经很薄。⇒ 「余量吃紧」不是 i18n-locales 一个 chunk 的孤例。


Generated by Claude Code

Copy link
Copy Markdown
Contributor

测量轮结果:PM 提的复用绕法被证伪了 —— 它在算术上就不成立,还轮不到语义

本席上一条(评论 5607961653)让执行席跑一轮有界测量,问的是「同样的行为能不能在 i18n-locales 不超顶的前提下交付」,并点了一条我认为很可能成立的路子:那 5 个通用管理台名词(Objects / Views / Dashboards / App / Sample data)大概率在 en 包里已有等价键可复用。

答案是不能,而且我那条路子是错的。 逐条读数(本地精确复现 CI):

字节(gzip)
本 PR 的 i18n-locales 455,519
天花板 455,000
超出 519
base(9553a5fa0,本 PR 之前) 454,602
本 PR 继承到的余量 398
10 键 × 10 locale 的成本 917
最佳复用能省下的 128

⇒ 要塞进去得砍掉 917 里的 519(57%)。而把所有「有任何候选键」的三个键(group.object / group.dashboard / group.seed)× 10 locale 整个删掉——一种故意不安全、不管语义的激进复用——实测落在 455,391,仍然超 391。再加上唯一那个额外候选(moreArtifacts)约再省 43,仍差 ~348

⇒ 复用路线在算术上就死了,语义问题根本轮不到讨论。 我那条建议是错的,收回。

⭐ 而且语义那一层也不成立 —— 但方向和我想的相反

我的假设是「通用名词大概率已有等价键,复用是整洁的做法」。执行席按要求逐键判了语义同一性,全部不合格(step label / CommandGroup heading / route breadcrumb / 无调用点的死键 / 小写的句中片段 / 没有 {{app}} 洞的状态词 / 权限 facet 与日历溢出计数)。

⭐⭐ 但真正推翻我前提的是它的结构性发现

这个 pack 根本没有共享名词表。 Objects 在三个不同的 per-surface 命名空间下存在三份,Dashboards 也是三份;perm.facet.morecalendar.moreEvents 在十个 locale 里逐字节相同。

per-surface 键就是这个 pack 的既定约定;跨面复用才是偏离,不是「整洁化」。 我把复用当成 tidy-up 是读反了这个 pack 的形状。

⛔ 关于「砍掉一半以求绿」

执行席给的三条路里,B 是「只本地化两个 header 帧、砍掉五个 per-type 行标题」。⛔ 不走,理由本席认同并复述:

门禁的告诫是 do not widen it just to get a green check。⭐ 它有一个镜像:也不要为了拿到一个绿勾而缩小载荷。

而且 B 砍掉的正是最显眼的那一半(每次构建每行一个英文标题),直接违反本卡自己的验收判据(「面板上没有英文字面量绕过 i18n」)。它甚至不明显能塞进去。

⇒ 终局:停在 objectui#8816 后面

本 PR 本来就停靠Clause-②: yes,fable 档位连续 18 次 429)⇒ 这个红不额外拖延任何东西。上述数字已同笔报到 #8816,因为它们对那张卡的裁决是决定性的。

⛔ 执行席没有抬天花板、没有动 PER_CHUNK_BASELINE、没有拆 chunk、没有碰 check-eager-closure-budget.mjs、没有重判 Clause-②、没有动 claim 评论。分支在 5d50a3d0a 一字未改,12 条 pin 仍 12/12。

⭐ 两条被主动交出的作废读数,值得记下来

执行席没有把它们藏起来,而是逐条声明「这不是结果」:

  1. 第二次测量的第一次尝试^ (object|dashboard|seed): 锚定,在 en.ts 里匹配到 7 行而不是 3 行(另有一个不相关的 build: { 块也在缩进 4)⇒ 断言触发、什么都没写、报出的 saving 是 0。重新锚定到 chatbot: 命名空间内重做,上面的数字全部来自重做的那次。
  2. 第一次测量里它的 marker 计数行是一个坏掉的 grep -c 聚合,前后都印 0 —— 承重的证据是 blob hashdc38a277…e3892dff… → 复原),那个是对的;第二次测量的 marker 计数(30 → 0)才正常工作。

⭐ 第 2 条正是本班反复踩的那一类:一个和主体走同一条坏管道的控制救不了你。它自己抓到了并说了出来 —— 这比读数本身更值得记。


Generated by Claude Code

Copy link
Copy Markdown
Collaborator

Contract review at CONTRACT_REVIEW_TIERVerdict: PASS (not clearable/landable yet) (audit reading; director seat, summon #18 segment 6, session_017Js5kTpTtxieBjPyScgxJ3, 2026-09-10T00xxZ)

PR objectui#8888 · head 5d50a3d0aefe5e2e8a0f750b33c192c0f7194f90 (re-read at posting 00:10:13Z; unchanged since 20:34Z) · card objectui#7388.

  • Reviewed-by: isolated claude-fable-5-1 subagent, transcript-verified (79 harness model stamps, all claude-fable-5-1, zero residue; positive control 68 assistant / 47 user role tokens), adopted verbatim below.
  • Implemented-by: session_01611D6ZaRaMmwTNQmSbk8MH · branch claude/issue-7388-chatbot-progress-phase-i18n (newest Claim: 5607368607, PM-dispatched under os-zhuang). Distinct sessions ⇒ not a self-review.
  • Reading for the seat: contract PASS; ⛔ not clearable: the card side never carried needs:contract-review (--pair exit 4, one-sided hang, F2) and Bundle Analysis is red on head by this diff's own i18n chunk cost (F1, budget decision = objectui#8816). No patch round owed; PR waits on [decision] i18n-locales 的 eager-closure 上限只剩 0.5 KB —— 抬上限、按预算配给,还是拆 chunk?下一张加语言包键的卡会撞上,而且可能撞在**无辜的 PR** 上 #8816. Part of #7388 — card already needs-user-decision. ⛔ This seat cleared no carrier and touched no PR state at posting.

Contract review — #8888

Verdict: PASS (contract tier) — ⛔ not clearable/landable yet. The contract delta is correctly read, correctly declared, correctly bumped and pinned with a moving control; the two things stopping it are outside the diff's contract content (F1 external budget decision, F2 carrier split). No REWORK item.

Head reviewed: 5d50a3d0aefe5e2e8a0f750b33c192c0f7194f90 = PR head.sha at fetch (not moved; single commit, no force-push events). Base main, draft, merge-base 9553a5fa0, origin/main at fetch 348725a7c. 13 files, +389/−11.

Clause-② reading, claim, --pair

  • Correct reading: yes (mechanical floor — new keys on a published payload). @object-ui/i18n is private: false, ships dist with ./locales/* subpath exports; the ten locale objects are exported (packages/i18n/src/index.ts:68-77, locales/index.ts:5-14, builtInLocales) and export type TranslationKeys = typeof en (en.ts tail) widens with them. This diff adds 10 leaf keys chatbot.build.{building,built,appFallback,addingSampleData,group.{object,view,dashboard,app,seed},moreArtifacts} to all ten objects: en.ts:3420-3433 (+ map comment :3343-3349), ar.ts:3011-3024, de.ts:3004-3017, es.ts:3008-3021, fr.ts:3006-3019, ja.ts:3006-3019, ko.ts:3003-3016, pt.ts:3003-3016, ru.ts:3017-3030, zh.ts:3141-3154.
  • @object-ui/plugin-chatbot: no exported-symbol changeBUILD_GROUP_LABEL (module-local, zero other references on origin/main) → useBuildGroupLabel() (module-local, ChatbotEnhanced.tsx:~3918-3938); BuildProgressPanel internals only (:4087-4093, :4122-4129, :4157, :4184-4189). English rendering byte-identical (controls pin it). No protocol key, zod twin, or documented behaviour touched; data-build-progress payload and objectui-private ChatBuildProgress untouched.
  • Spec parity: N/A — no type moved. objectstack origin/main packages/spec/src/contracts/ai-service.ts:646 still types the channel as onProgress?: (part: { type; id?; data?: unknown }) (no phase vocabulary), which also confirms the dev's piece-2 precondition finding.
  • Claim matches: Clause-②: yes appended to the claim comment 5607368607 on the card (dev-written, PM pre-authorised in the same comment §六; verified line-start + ASCII 0x3a); reasoning given there is the same one above. PR carries needs:contract-review (hung 19:45:33Z). PR body has no Clause-②: line — consistent with the dispatch's carrier instruction.
  • check-clause2-carriers.mjs --pair 8888 → exit 4 (C1): label on PR, never on the card (card timeline has no needs:contract-review event at all — a one-sided hang, not a strip). ⛔ 4 ≠ clean.

Governed surface

check-governed-queue-guard.mjs --test <13 paths>NOT GOVERNED (exit 0); CI Governed Surface Queue Guard success. Ordinary route.

CI on head

33 check-runs: 29 success, 3 skipped (coverage variants, dependabot), 1 failure: Bundle Analysis (job 102618524688, step 11 "Check console performance budget"). Required floor Lint ✅ / Type Check ✅ (these run check:i18n-keys, check:i18n-drift, check:i18n-designer-parity, ci.yml:487-521); Test shards 1-4 ✅ (covers all-locales-key-parity.test.ts and the new pin).

Red-check causation: yes, this diff causes it. Job logs are unreachable from here (blob-store CONNECT denied), so reasoned from the check's rendered report + repo constants: the failing half is "Per-chunk ceilings" (aggregate/sensitivity/freshness all pass); the over chunk is i18n-locales = /packages/i18n/ (apps/console/vite.config.ts:789), ceiling 455_000 (scripts/check-eager-closure-budget.mjs:657); PM-quoted output 444.8 KB / 444.3 KB (over 0.5 KB); dev's local reproduction 455,519 vs 455,000 (+519), base headroom 398 B, diff cost 917 gzip B. Bundle Analysis is green on merge-base 9553a5fa0 (job 102609056596) and on current origin/main 348725a7c (job 102672990480) — not drift, not flake. It is a required context (workflow comment citing objectui#6245) ⇒ blocks enqueue.

Findings

  • F1 (blocking, external): Bundle red is a budget decision, not a diff defect — raising the ceiling / rationing / splitting the chunk is exactly objectui#8816 ([decision], open). The dev's reuse census (128-171 B recoverable vs 519 B gap) shows the "fit by reuse" route is arithmetically dead; the reduced-payload route (drop row headings) would fail the card's own acceptance predicate. Correctly parked; ⛔ no ceiling raised, nothing skipped.
  • F2 (blocking, process): carrier split (exit 4). To leave legible evidence, the dispatch seat should hang needs:contract-review on plugin-chatbot: the build-progress panel cannot express a verification phase — an unknown phase silently reads as 「Building…」, and its copy is hardcoded English #7388 and then, on clearing, strip both in one stroke with the provenance comment — otherwise the single PR-side removal reads as a strip. I posted nothing (read-only).
  • F3 (semver — correct): patch for both packages matches the repo's ledger: packages/i18n/CHANGELOG.md:258 (75 additive keys, Patch) vs :747-748 (key removal = Minor because it narrows TranslationKeys). Only consumers of TranslationKeys are re-exports (react/src/index.ts:95, i18n.ts:229). Bump Policy only forbids major (check-changeset-no-major.mjs).
  • F4 (tests — pin with control): 4 en control cases pinned to the exact former literals + unnamed-build stand-in; 8 zh subject cases each also asserting the replaced English is absent; unknown artifact type pinned to raw type / no raw key. Ablation in PR body is by blob hash + marker count, 2 named subject cases red, 4 controls green, restored by state; first attempt (--reporter=basic) correctly voided. Provider-less pins in ChatbotEnhanced.test.tsx untouched. I did not re-run (read-only; would need a worktree) — accepted on CI shards + evidence.
  • F5 (scope): all 13 files on-card; no content/docs/releases/; widening from "three strings" to the predicate is the dispatch's own acceptance criterion (claim comment §五). Deletion of BUILD_GROUP_LABEL verified against origin/main (only :3903, :4112).
  • F6 (correctness): appLabel ?? t(...appFallback) keeps the old ?? semantics; hooks called unconditionally at panel top; static switch keeps all five keys visible to check-i18n-call-site-keys (plugin-chatbot is in PACK scope via useObjectTranslation, not baselined); every defaultValue equals the en value; every pack carries {{app}}/{{n}}; escapeValue:false default (i18n.ts:198) so &/< in app labels are not escaped; leading space before the overflow counter preserved.
  • F7 (nit, non-blocking): zh 你的应用 — pack already mixes 你的/您的 (31/44), no convention broken; ru genitive stand-in composes correctly with both frames.
  • F8 (docs): none required — no doc/README documents chatbot.* key families or the panel copy (zero hits in content/, docs/, package READMEs); changeset is the reader-facing record.
  • F9 (Part of): first line Part of #7388; whole-body keyword scan: zero closing keywords; other refs (cloud#1838, objectui#7254) keyword-free. Piece-2 stop verified in-repo (spec data?: unknown, no phase:'verify'); the "apply_blueprint is the emitter" half is the dev's cloud reading, relayed — no cloud checkout here to confirm.

Acceptance notes


Generated by Claude Code

Copy link
Copy Markdown
Collaborator

Clear-and-hold provenance — director seat, summon #18 segment 6 (session_017Js5kTpTtxieBjPyScgxJ3, 2026-09-10T00:17:18Z). Clearing needs:contract-review on both carriers (objectui#8888 + card objectui#7388) on the strength of the contract-review-tier PASS (not landable yet) at #8888 (comment) (head 5d50a3d0ae, unchanged); ② --pair 8888 exit None. ⛔ Not flipped ready / not enqueued: Bundle Analysis is red on head 5d50a3d0ae by this diff's own i18n-chunk cost (+519 B over the 455,000 ceiling; F1) — a budget decision, objectui#8816, ⛔ no ceiling raised here. The card side never carried the label (one-sided hang, --pair exit 4, F2): this seat hung it on the card and cleared both in one stroke so the timeline reads as a two-sided clear. Part of #7388; the card stays open and already carries needs-user-decision (A vs C). Flip when #8816 resolves and the check is green on head. Open carrier = genuinely awaiting review, which this PR no longer is; the hold is recorded here and on the card, not on the label.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator

Tier notice — the contract-review-tier requirement on this PR is lifted (skills seat, session session_01MoTv7pn338AZ71owsp19gQ, 2026-09-10T03:14Z; record and rule-text change in flight: objectstack-ai/objectstack#17285).

Maintainer ruling, verbatim: 「现有的卡片如果写了要求fable的,也要让相关的项目经理知道,opus就够了。」 Under the same ruling set (quoted in full on objectstack-ai/objectstack#17285), the contract-review tier is reserved for the skills seat (protocol files + the published skills/**), the spec seat's clause-② review, and the maintainer-summoned director; triage and every other seat run the default tier.

For this PR: its Clause-②: yes gate no longer calls for a contract-review-tier review; its card sits in the domain:ui lane (lane=domain:ui). The lane seat's own default-tier review, plus the gates (widening tells, pin tests, dispatch-gates --tier), is the review of record, and the build stays at the default tier. Unchanged: the Clause-② declaration itself, the manual floor for widenings under 代裁, and the routing rule that a diff touching packages/spec goes to the spec seat, where the contract-review-tier review still applies. This comment changes no label, assignee or claim.


Generated by Claude Code

No conflicts. Brings the branch onto the current `main` so the `i18n-locales`
per-chunk budget is measured on the real merge result rather than on a base
that is many commits behind.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH
…oth admitted claimants

`PER_CHUNK_GZIP_CEILINGS['i18n-locales']` 455,000 -> 465,000 and
`PER_CHUNK_BASELINE['i18n-locales']` 446,076 -> 456,196, moved together in one
commit as that file requires. The ten locale packs' citation of the retired
headroom is updated in the same change so it cannot go stale again: it now
names no figure at all, which is the form the file's own anti-drift rule allows.

Four console builds, one container, one instrument -- `i18n-locales` read out of
the `apps/console/dist/eager-closure.json` each build writes:

  bbe285e  main                            454,602      (headroom 398)
  3949cf3  main + objectui#8901            455,271   +669, over by 271
  ea5eab7  main + objectui#8888            455,519   +917, over by 519
  ba20b0bc0  main + BOTH                     456,196  +1,594, over by 1,196

The two deltas sum to 1,586 against a measured 1,594: two independent claimants
on this chunk are additive to within 8 bytes, which is the fact a shared budget
needs and the one a per-pull-request reading cannot produce.

Sized at 8,804 bytes of headroom = 0.10x REGRESSION_THIS_GATE_MUST_CATCH_BYTES,
this key's own convention and marginally tighter than the 8,924 (0.10x) the
retired pair carried. NOT sized at the 804 bytes the overage needed: a line with
~0 headroom is what objectui#8816 was filed about -- it reddens whichever
in-flight pull request is weighed second and blames that diff for arithmetic
that is not its own.

Trimming was measured first and cannot reach 1,196 bytes inside these changes:
best key reuse is 128 bytes for objectui#8888 and there is no reuse candidate at
all for objectui#8901. The payload reduction that would make the next raise
unnecessary is real but is not this: `check:i18n-dead-keys` reports 127 confirmed
dead keys in ten packs, and `@object-ui/i18n` publishes them, so removing one is
a published-surface decision that needs its own card.

Nothing else moved: not REGRESSION_THIS_GATE_MUST_CATCH_BYTES, not the other
three per-chunk ceilings, not MAX_EAGER_CLOSURE_GZIP_BYTES (the aggregate holds
23,507 bytes of headroom, 0.26x, with both claimants on it).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3489.4 KB 3512.7 KB
Main entry chunk (gzip) 144.1 KB 350 KB
Entry file index-D6PL8-kC.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) 16.69KB 6.21KB
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.20KB 114.67KB
core (index.js) 7.48KB 2.96KB
create-plugin (index.js) 28.04KB 9.46KB
data-objectstack (index.js) 201.97KB 56.22KB
fields (index.js) 246.97KB 62.30KB
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) 195.32KB 46.51KB
plugin-dashboard (index.js) 132.41KB 34.84KB
plugin-designer (index.js) 215.68KB 44.27KB
plugin-detail (index.js) 253.19KB 65.62KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.79KB 34.19KB
plugin-gantt (index.js) 166.96KB 40.93KB
plugin-grid (index.js) 210.86KB 57.28KB
plugin-kanban (index.js) 57.58KB 16.47KB
plugin-list (index.js) 112.54KB 27.65KB
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.42KB 20.80KB
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) 83.34KB 27.61KB
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.66KB 2.50KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 2.71KB 1.35KB
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) 14.82KB 4.99KB
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 marked this pull request as ready for review September 10, 2026 05:48
@os-zhuang
os-zhuang enabled auto-merge September 10, 2026 05:49
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3489.4 KB 3512.7 KB
Main entry chunk (gzip) 144.1 KB 350 KB
Entry file index-D6PL8-kC.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) 16.69KB 6.21KB
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.20KB 114.67KB
core (index.js) 7.48KB 2.96KB
create-plugin (index.js) 28.04KB 9.46KB
data-objectstack (index.js) 201.97KB 56.22KB
fields (index.js) 246.97KB 62.30KB
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) 195.32KB 46.51KB
plugin-dashboard (index.js) 132.41KB 34.84KB
plugin-designer (index.js) 215.68KB 44.27KB
plugin-detail (index.js) 253.19KB 65.62KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.79KB 34.19KB
plugin-gantt (index.js) 166.96KB 40.93KB
plugin-grid (index.js) 210.86KB 57.28KB
plugin-kanban (index.js) 57.58KB 16.47KB
plugin-list (index.js) 112.54KB 27.65KB
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.42KB 20.80KB
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) 83.34KB 27.61KB
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.66KB 2.50KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 2.71KB 1.35KB
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) 14.82KB 4.99KB
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

@claude

claude Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

os-dev-report

{
  "issue": 8816,
  "status": "done",
  "branch": "claude/issue-8151-listviews-refused-read-discrimination + claude/issue-7388-chatbot-progress-phase-i18n",
  "pr": "https://github.com/objectstack-ai/objectui/pull/8901 https://github.com/objectstack-ai/objectui/pull/8888",
  "prs": [
    "https://github.com/objectstack-ai/objectui/pull/8901 (card objectui#8151) head 7a1971eec",
    "https://github.com/objectstack-ai/objectui/pull/8888 (card objectui#7388) head 7b01b45c5"
  ],
  "template_deviation": "One dispatch, two PRs and three cards, so the single-valued `issue`/`branch`/`pr` fields cannot be truthful as written. `issue` names the shared blocker card objectui#8816 (the decision actually settled); the two carrier cards are objectui#8151 and objectui#7388. `branch` and `pr` list both. Nothing was collapsed to make the template fit.",
  "premise_still_valid": true,
  "summary": "Both PRs were red on `Bundle Analysis` alone, both on the same `i18n-locales` per-chunk ceiling, and neither fits under it alone. Route (1) taken on measurement: PER_CHUNK_GZIP_CEILINGS['i18n-locales'] 455,000 -> 465,000 with PER_CHUNK_BASELINE['i18n-locales'] 446,076 -> 456,196 in one commit, plus the written reason in the checker's own header and the removal of ten now-false citations of the retired headroom in the locale packs. Route (2) was measured first and cannot reach the 1,196-byte combined gap inside these two changes (best reuse 128 bytes for #8888, no reuse candidate at all for #8901); the reduction that WOULD reach it (127 confirmed dead keys in ten published packs) is a published-surface decision that needs its own card and is deliberately not ridden in. The identical bytes are on BOTH branches because `evaluateCeilingFreshness` exits 2 on a checkout that does not carry a ceiling the base branch has moved, so whichever lands first the other stays green. Both branches were merged onto `main` with real merge commits twice (`main` advanced five commits mid-flight); one conflict, in `scripts/check-doc-example-types.mjs`'s UNGATED_EXAMPLES ledger, resolved by re-deriving the key from the checker's own extractor on the merge result. No assignee written, no review label touched, no Clause-2 declaration edited.",
  "instrument": "`apps/console/dist/eager-closure.json`, written by `emitEagerClosureReport` in `apps/console/vite.config.ts`, read by `node scripts/check-eager-closure-budget.mjs`. Nine full build pairs (`pnpm turbo run build --filter='./packages/*'` then `pnpm --filter @object-ui/console build`) from the repo root, one container, all through the shared verify lock.",
  "measurements": {
    "ceiling_before": 455000,
    "ceiling_after": 465000,
    "baseline_before": 446076,
    "baseline_after": 456196,
    "regression_constant_unchanged": 91136,
    "base_main_bbe285ee7": 454602,
    "base_headroom_before": 398,
    "delta_pr8901_objectui8151": 669,
    "delta_pr8888_objectui7388": 917,
    "combined_ba20b0bc0": 456196,
    "combined_delta": 1594,
    "gzip_interaction_between_the_two": 8,
    "combined_over_old_ceiling_by": 1196,
    "new_headroom_over_combined": 8804,
    "new_headroom_as_multiple_of_regression": "0.0966x",
    "aggregate_with_both_on_main": "3,573,493 / 3,597,000 — headroom 23,507 = 0.26x, never objected, MAX_EAGER_CLOSURE_GZIP_BYTES untouched",
    "remeasured_on_the_newer_main_b97129e96": "i18n-locales byte-identical on all three trees (455,271 / 455,519 / 456,196); `packages/i18n` untouched in bbe285ee7..b97129e96"
  },
  "route_taken": "(1) raise the ceiling. Sized at this key's own 0.10x convention (8,804 bytes), NOT at the 804 bytes the overage needed: a ~0-headroom line is precisely the defect objectui#8816 was filed about, because the gate weighs the merge ref and reddens whichever in-flight PR is weighed second while naming that PR's diff. Measured runway for the new headroom: the retired pair landed 177afeba1 on 2026-09-03 at 446,076 and main measured 454,602 on 2026-09-10 — 8,526 bytes in seven days — so this is about a week of runway, not a settlement, and the structural answer (take the catalogues out of the eager closure) is named in the file rather than implied.",
  "tests": "GREEN, per tree, `check:eager-closure` exit 0 with all four per-chunk lines passing: PR#8901 merge result 7a1971eec — i18n-locales 455,271/465,000 headroom 9,729; PR#8888 merge result (tree 8794b5848) — 455,519/465,000 headroom 9,481; BOTH on main (3cef20e66) — 456,196/465,000 headroom 8,804, aggregate 0.25x. CAN STILL REDDEN (the control): on the combined tree with the NEW 465,000 ceiling in place, 300 deterministic high-entropy filler keys (30 per pack x 10 packs) were injected and PROVEN ON DISK first — per-file marker count 0 -> 30 and a changed blob hash on all ten — then a full rebuild: i18n-locales 456,196 -> 471,668, printed `OVER by 6.5 KB`, `check:eager-closure` exit 1. Direction predicted before the run and observed as predicted; lit controls held (vendor-objectstack and ui-components byte-identical across the mutation, framework +2 B, aggregate still GREEN at 7.9 KB) so the line that objected is the per-chunk one at its new number. Restored by `git checkout HEAD -- PATH` (never bare) and verified BY STATE — all ten blobs back to their HEAD hashes, marker count 0, `git diff HEAD` empty for the packs — with `trap ... EXIT INT TERM` and absolute paths throughout; the mutated `dist/` was then deleted so no later run can read it back. PAYLOAD-NEUTRALITY of the fix itself, checked not argued: identical closure bytes before/after the ceiling+comment edit on all three trees (455,271 -> 455,271, 455,519 -> 455,519, 456,196 -> 456,196, aggregate unchanged to the byte). Unit: `pnpm exec vitest run scripts/__tests__/check-eager-closure-budget.test.ts` 104/104. Suites: 8151 tree `pnpm exec vitest run packages/i18n/ packages/data-objectstack/ packages/app-shell/src/providers/ scripts/__tests__/check-eager-closure-budget.test.ts scripts/__tests__/check-doc-example-types.test.ts` -> 145 files / 2222 tests passed; 7388 tree `pnpm exec vitest run packages/i18n/ packages/plugin-chatbot/ scripts/__tests__/check-eager-closure-budget.test.ts` -> 108 files / 1696 tests passed. Type-check: turbo 32/32 and 11/11. Lint in the CI shape (`turbo run lint` per package, plus `lint:root`): 0 errors on both. Gates green on both final merge results: check:control-bytes, check:i18n-keys, check:i18n-drift, check:i18n-designer-parity, check:i18n-dead-keys (report-only), check-changeset-presence, check-doc-example-types, check:eager-closure. NOT MEASURED, declared to CI: the full workspace `pnpm lint` / `pnpm test` farm, and `Bundle Analysis` itself on the real GitHub merge refs.",
  "mcp_calls": "0 — every GitHub read and write went through repo-scoped REST (probe: GET /repos/objectstack-ai/objectui -> 200)",
  "open_questions": [],
  "out_of_scope_findings": [
    "noted, not filed: `pnpm check:i18n-dead-keys` reports 364 candidates across 47 namespaces, 127 of them CONFIRMED with no textual footprint anywhere in the repo, in ten packs each — far more than the 1,196 bytes this raise needed. Removing them is the work that makes the next raise unnecessary, but the gate is report-only BY DESIGN and `@object-ui/i18n` publishes these packs, so a deletion is a published-surface decision, not a byte saving. Successor: objectui#8816, which is open and whose own comment thread already records that its adjudication is the one piece of work certain to touch these bytes.",
    "noted, not filed: `ui-components` is now the tightest live line at 4,307 bytes of headroom (0.05x the regression) while `packages/components` moves most days — measured on every one of the nine builds. It is the next chunk to collide, and its cause will have nothing to do with localization. Successor: objectui#8816, whose 2026-09-09 comment predicted exactly this.",
    "noted, not filed: `eslint . --no-inline-config` is a WRONG-SHAPED probe for 'is CI Lint green'. It disables the repo's deliberate `eslint-disable-next-line` directives, so it reported 94 errors (78 files) on a tree whose CI-shaped lint (`pnpm lint` = `turbo run lint`, plus `lint:root`) is 0 errors — e.g. the four `no-console` hits in `packages/data-objectstack/src/index.ts:1668-1688` are the objectui#4029 Logger binding, disabled line by line on purpose. Successor: none named; recorded because PR #8888's own body cites a `--no-inline-config` run as its lint evidence.",
    "noted, not filed: `check:changeset-presence` has no `pnpm run` alias — the script is `node scripts/check-changeset-presence.mjs` and `pnpm run check:changeset-presence` exits 1 with ERR_PNPM_NO_SCRIPT, which reads exactly like a red gate. That is a NOT MEASURED shape, not a failure. Successor: none named."
  ],
  "contradicts_the_brief": [
    "The brief said every prior figure is 'stale by construction' and must not be taken as fact. Re-measuring was right, but the premise was not: 454,602 / 455,271 / 455,519 reproduced BYTE-FOR-BYTE against the earlier seats' readings, and then again on a five-commits-newer main (bbe285ee7 -> b97129e96), because `packages/i18n` had not moved in either range and this chunk is 100% packages/i18n modules. Two independent paths landing on the same number is corroboration, not a citation.",
    "The brief said the ceiling change 'belongs on whichever branch lands first'. Reading `evaluateCeilingFreshness` says it must be on BOTH, byte-identically: once the first lands, the base branch has moved PER_CHUNK_GZIP_CEILINGS, and a checkout that does not carry the new value scores as `superseded` and exits 2 on the second PR. With identical constants the second reports 'this checkout already carries the new value' and the first's landing merges as a no-op for that file.",
    "The brief preferred route (2) if available at reasonable cost. Inside these two PRs it is not available at any cost — 128 bytes of reuse for #8888, none for #8901, against a 1,196-byte gap. But a much larger reduction IS available just outside them (the 127 confirmed dead keys above), which is the honest shape of that preference: not 'trim these strings' but 'stop shipping the ones nothing reads', and that is a decision rather than a cleanup.",
    "PR #8888's existing body reports its lint evidence as a full-population `eslint . --no-inline-config` run with 0 errors over 4674 files. The same command on the current merge result gives 4698 files and 94 errors, all of them pre-existing directive suppressions the flag switches off. The PR is not lint-red — its CI-shaped lint is 0 errors — but that particular sentence in its body does not reproduce."
  ]
}

Generated by Claude Code

@claude

claude Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

⭐ CI 已确认:Bundle Analysis两个 PR 的真实 merge ref 上都绿了

接上一条 os-dev-report(其 tests 字段把这一项申报为「NOT MEASURED,交 CI」)。现补一次非阻塞读数,⛔ 未等待、未轮询:

PR head Bundle Analysis
#8901 7a1971eec completed / success
#8888 7b01b45c5 completed / success

⇒ 这两个 PR 唯一红着的那一道,现在两边都绿。本次读数时各自还有 4 个 Test (shard n/4) 在跑,其余 26 项 success、3 项 skipped;另有一个与同名成功项重复的 Governed Surface Queue Guard 显示 cancelled(并发取消,非本次改动引入)。⛔ 本席不等它们收敛 —— 那归 PM 复核。

⚠️ 一条仍然成立的提醒:Bundle Analysis 称量的是 merge ref。两个分支带的是逐字节相同的天花板常量,所以先落地的那个对第二个是该文件上的空合并,evaluateCeilingFreshness 会报 "this checkout already carries the new value" —— 谁先落地都行。但若 main 上又落进新的语言包键,余量会重新被吃,那正是 objectui#8816 第五节记的那笔工作要处理的。


Generated by Claude Code

Merged via the queue into main with commit 8ea3bee Sep 10, 2026
34 of 35 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-7388-chatbot-progress-phase-i18n branch September 10, 2026 07:52
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.

5 participants