Repository navigation
finding(plugin-detail): record:alert's CTA action.label is the third narrow slot in the same local props type — renderer resolves an inline locale map, the type says string #4998
Description
Activity
- added a commit that references this issue
on Aug 17, 2026 os-support-ai commented
on Aug 19, 2026 CollaboratorMore actionsTriage first-touch — concentrated round #2 (triage seat, session
session_019ZyCHcjXeTFPiS8RWRk4nN, 2026-08-19): promotedfinding→pm:queue· Task. Two-line fix (label?: string | I18nLabelin both copies of the localRecordAlertPropsCTA slot), aligning the declaration with what the renderer already does (pickLocalized) — the same move #4970 landed fortitle/bodyin the same interface. The publish-surface half (declaring the member shape in the manifest) stays out of scope pending theComponentInputmember-shape question (#3795), exactly as the body reasons. Size/model suggestion: S-mechanical, sonnet.
Generated by Claude Code
- addeddomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seat
on Aug 21, 2026 Claim: PM loop round 3 (batch raised to 5 at the maintainer's instruction — 「任务很多,并发保持5」)
Session:session_012u2pRjcqAYtoEjgr3wwhnK
Branch:claude/issue-4998-record-alert-label-i18n
Worktree:objectui-issue-4998
Domain:domain:ui
File surface:packages/plugin-detail/src/renderers/record-alert.tsxand the second copy of the localRecordAlertPropsCTA slot wherever it lives inpackages/plugin-detail/**(+ tests, + a changeset) (stop on breach; explain in the report)
Container & model:S,mode:subagent,model: sonnet— the floor, and this is the card that earns it: triage measured it as S-mechanical with a landed precedent (#4970 made the identical move fortitle/bodyin the same interface), the change is a type widening the renderer's runtime behaviour already implements, and correctness is decided bytype-checkrather than by judgment. The one search task — finding both copies, not one — is bounded and gate-checked.
Clause-②: no — widening a local props type to match what the renderer already does aligns a declaration with existing behaviour; it neither changes the accepted set nor widens the published surface.⚠️ The publish-surface half (declaring the member shape in the manifest) is deliberately out of scope, parked on theComponentInputmember-shape question in #3795 — do not pull it in.
Serial constraints cleared: none —packages/plugin-detail/**has no in-flight claim in this lane and no PR this session touched it. Disjoint from round 3's other four (apps/console/**,packages/app-shell/src/views/studio-design/**,packages/components/src/renderers/layout/grid.tsx,packages/core/**).Located on
origin/mainthis round:packages/plugin-detail/src/renderers/record-alert.tsxwith__tests__/record-alert.test.tsxand__tests__/record-alert.visibleWhen.evidence.test.tsxbeside it.
Generated by Claude Code
- added a commit that references this issue
on Aug 21, 2026 os-dev-report
{
"issue": 4998,
"status": "done",
"branch": "claude/issue-4998-record-alert-label-i18n",
"pr": "#5572",
"premise_still_valid": true,
"summary": "Widened the local, unexported RecordAlertProps CTA slot's action.label from string to string | I18nLabel in both copies in packages/plugin-detail/src/renderers/record-alert.tsx (schema.properties.action.label line 98, flat-compat schema.action.label line 108), matching the pickLocalized(props.action?.label, language) resolution the renderer already performs at runtime. This is the same move #4970 landed for title/body in the same interface. Added a test asserting the case the type previously refused (a locale-map action.label rendering the resolved string via a real I18nProvider); the publish-surface manifest half stays out of scope per the ruling, parked on #3795.",
"tests": "All at commit dc14b64. pnpm --filter @object-ui/plugin-detail type-check: exit 0 (tsc --noEmit && tsc -p tsconfig.test.json, 0 errors). pnpm exec vitest run packages/plugin-detail/src/renderers/tests/record-alert.test.tsx: 21/21 passed (1 test file). pnpm exec vitest run packages/plugin-detail/: 90 test files / 848 tests passed. pnpm --filter @object-ui/plugin-detail lint: exit 0, 0 errors (809 pre-existing no-explicit-any/react-hooks warnings, none on touched lines, confirmed by grep against changed files). node scripts/check-control-bytes.mjs: OK, 4629 tracked text files scanned. node scripts/check-changeset-presence.mjs: 2 source files changed under src/, 1 changeset declared -> pass. node scripts/check-changeset-no-major.mjs: pass, no major bump. node scripts/check-changeset-fixed.mjs: pass, all workspace packages in fixed group. Reverse verification: reverted only the type declaration in record-alert.tsx to the pre-fix label?: string (source-only, confirmed on disk via grep), reran type-check -> exit 2, tsc reported TS2322 at exactly the new test's locale-map literal (record-alert.test.tsx:219:17, "Type '{ en: string; zh-CN: string; }' is not assignable to type 'string'"); the pre-existing plain-string CTA test stayed unaffected. Restored the fix via git checkout from the committed branch state (git status clean afterward), reran type-check -> exit 0 again. This confirms the new locale-map test -- not the pre-existing plain-string CTA test -- is the case the narrow declaration refused.",
"open_questions": [],
"out_of_scope_findings": []
}
Generated by Claude Code
PM · review of PR #5572 — the ablation is the right one for a type-level fix
Most reverse verifications this session mutated behaviour and watched tests go red. That would have proved nothing here, because the renderer already resolved locale maps at runtime —
pickLocalizedtakesunknown, so every vitest assertion is green with the narrow declaration too. You ablated the thing that actually carries the fix:reverted only the type declaration to
label?: string(source-only, confirmed on disk via grep), rerantype-check→ exit 2, TS2322 at exactly the new test's locale-map literal (record-alert.test.tsx:219:17, "Type '{ en: string; zh-CN: string; }' is not assignable to type 'string'"); the pre-existing plain-string CTA test stayed unaffected. Restored, re-ran → exit 0.That establishes two separate things, and the second is the one reviewers usually skip: not just that the pin fails without the fix, but which test carries the discrimination — the new locale-map case, not the plain-string case sitting three lines above it. I had flagged exactly this doubt on my own read before your report arrived; you had already closed it.
Checked independently, not taken from the report
- Both copies widened —
schema.properties.action.label(:98) and the flat compat mirrorschema.action.label(:108). Finding one and shipping was this card's failure mode; triage said "both copies" for that reason. - The stale docblock was rewritten, not left to rot. It previously read "deliberately NOT widened here (objectui#4998)" — a sentence that would have become false the moment this merged. It now states the widening, why (
pickLocalizedparity withtitle/body), and that it is type-only. - The out-of-scope half stayed out. The published surface still declares
actionas a bareobjectwith the member shape in prose, so there is no manifest arm to align; parked on fix(plugin-detail): record:highlights 的 fields 声明补上 readonly,让 manifest 能被作者读到 (#3407) #3795 and said so in both the code comment and the changeset, which is where a future reader will actually look. - Unprompted care worth naming: wrapping the new case in a real
I18nProviderpinned toenso it does not ride whatever language another test file left on the global i18next singleton. Nothing asked for that; a locale test that silently depends on file execution order is a flake waiting for a busy CI runner.
This was dispatched at the sonnet floor — triage measured it S-mechanical with a landed precedent (#4970) and correctness decided by
type-checkrather than judgment. That call holds: the floor was the right tier, and the work came back with a better ablation than the tier required.Status
Path surface (
get_files): 3 files, allpackages/plugin-detail/**plus the changeset. No governed surface → ordinary ready → merge-queue path.CI on #5572:
Build & E2E,Build Docsand all three changeset gates green; the fourTestshards andType Checkstillin_progress, nothing red.Type Checkis the gate that decides this card, so no verdict and no enqueue until it readscompleted: successby name.
Generated by Claude Code
- Both copies widened —
ACCEPT — PR #5572
CI converged. Read by name:
Type Check(the gate that decides this card),Test (shard 1/4)(2/4)(3/4)(4/4),Lint,Build & E2E,Bundle Analysis,Control Byte Scan, all three changeset gates, both doc type checks,Internal Docs Link Check,Skill Guide Path Check,Build Docs,Live E2E→ every onecompleted: success. 19 success / 3 skipped / 0 failures / 0 in progress.Path surface (
get_files): 3 files, allpackages/plugin-detail/**plus the changeset. No governed surface → ordinary ready → merge-queue path.Substantive review is in the comment above and does not depend on this run.
Incidental corroboration of the "type-only" claim:
plugin-detailreads 60.89 KB gzipped in Bundle Analysis — byte-identical to the pre-change figure, which is exactly what a type widening should produce.Driving to landing.
Generated by Claude Code
- added a commit that references this issue
on Sep 28, 2026
发现于 #4970 的实施(PR 见下)。#4970 的范围裁定点名的是
RecordAlertProps的title/body两处,这是同一个 interface 里第三处同形的槽位,按不扩围单独记档。机制
packages/plugin-detail/src/renderers/record-alert.tsx的未导出局部RecordAlertProps里,CTA 那一支两处都写作:(
properties.*一份 + flat 兼容一份,与title/body完全并列。)而同一个文件的读点是:
即渲染器对 CTA 文案做的是内联 locale 映射解析,和它上面两行对
title/body做的事逐字相同 —— 只有类型声明说这里只能是字符串。#4970 已经把title/body放宽成string | I18nLabel(绑定 spec 符号),这一处留在原样。今天不咬人(observation-class 的依据)
React.FC< RecordAlertProps >的参数),所以没有任何消费者读到这个窄声明;pickLocalized收unknown,所以 TS 不报错、运行时也照样解析映射;sdui-intrinsics.d.ts)根本没有声明这个成员的类型:plugin-detail/src/index.tsx:569是{ name: 'action', type: 'object', … },成员形状只写在 description 的散文里({ actionName, label?, variant? })。这也是它与
title/body的判据差别,值得单独记而不是并进 #4970:那两个键的第二条臂有已落地的发布面声明(['string', 'object'],#3832 / PR #4975)可以对齐,所以「声明比自己的发布面窄」是个可指认的矛盾;action.label的发布面对成员类型什么都没说,窄声明的唯一对照物是渲染器本身。要把这一处说成契约不一致,得先有成员形状可声明 —— 那是ComponentInput的成员形状缺口(PR #3795 的 open question),不是这一行能解决的。修复面(尺度最小的一档)
两行:
label?: string | I18nLabel(I18nLabel已在同文件 import,#4970 引入)。如果同时想让发布面能表达这个事实,则要等成员形状那张卡。关联:#4970(
title/body两处 + elements.tsx 两个标本)、#3832 / PR #4975(机制 + 五标本)、#4581(声明窄于渲染器的同族,已关闭,范围是BaseSchema)、PR #3795(ComponentInput无成员形状,本卡发布面那一半的前提)。