fix(components,plugin-detail): element:text.content / element:button.label 声明真实联合,第 6、7 个标本停止对合法写入告警 (#4970) - #4999
Conversation
…label declare their real unions (#4970) The sixth and seventh specimens of the objectui#3832 contradiction: both inputs' own `description` teaches the author to write an inline translation map, both renderers resolve one through `pickLocalized`, and both spec props schemas accept one — while `type: 'string'` made the manifest gate report `type-mismatch` on exactly that shape. Both blocks are in `PUBLIC_BLOCKS`, so it reached authors through `sdui.manifest.json`, `sdui-intrinsics.d.ts` and the save gate. Each is now `type: ['string', 'object']`, the union form #3832 landed. The arms were re-measured on the `@objectstack/spec` 17.0.0 GA pin rather than copied from the issue (written at 17.0.0-rc.6): `ComponentPropsMap['element:text'].content` and `['element:button'].label` are both `string | Record< string, string >`, and both refuse a number, a boolean and an array. The acceptance test derives the expected arms from those verdicts instead of restating them, and pairs each specimen with controls — reverting `checkType`'s any-arm logic leaves the positive assertions vacuously green (an array-valued `type` falls into the old switch's `default: return null`), so the controls are the only half that moves. `record:alert`'s renderer-local `RecordAlertProps.title` / `.body` are widened to `string | I18nLabel` in the same pass: the same file resolves both through `pickLocalized` and the block's published `inputs` have declared `['string', 'object']` since #3832, leaving this unexported type as the last place in the package claiming otherwise. Bound to the spec symbol rather than re-spelling `Record<string, string>`, matching `BaseSchema.label` in `packages/types/src/base.ts`. Co-authored-by: Claude <noreply@anthropic.com>
…eaves narrow (#4970) The widened doc comment read as if RecordAlertProps had no narrow slot left, which is not true: action.label one level down is the same shape and was filed as #4998 rather than fixed here, because its published surface declares `action` as a bare object with the member shape in prose — there are no arms for it to be aligned against yet. Say so at both places a reader would otherwise take the stronger claim from. Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
|
PM 验收 ✅ ACCEPT(#4970,批次 20) 实物核验(merge-base CI 亲读:20/20 check runs completed,零失败(两项 path-filter skipped 计绿)。序列化面实测:公开 manifest 逐字节 diff 恰好 2 个 hunk(55/57 条目不动),单臂折叠约束是测出来的不是引述的。 反向验证读数:变异①的方向修正是本单最有价值的读数 —— 派发词沿用了 #3832 变异①的「对照红」预设,dev 指出只删声明臂、机制完好时动的应是正向半边(诊断照常产出),先写下修正预判再实测命中( finding #4998(action.label 第三同形槽,需 member-shape 机制,#3795 维)入池,不扩围判断正确。main 已前移无需手动 rebase —— merge queue 会按队列 ref 重算。undraft + auto-merge(squash)。 Generated by Claude Code |
Fixes #4970
#3832 的机制已随 PR #4975 落 main(
279fb139d),本卡是那张表漏掉的第 6、7 个同形标本,各一行;PM 在认领评论 5316410458 里把相邻观察(record-alert.tsx两处局部 props 类型)并入本卡一次收拾。前提验证(先做,后写码)—— 卡面实测是 rc.6 时代的,已在现 pin 上重取
卡正文的 spec 实测标注的是
@objectstack/spec 17.0.0-rc.6,当前 pin 已是 GA17.0.0(package.json/packages/components/packages/sdui-parser三处都是^17.0.0)。臂要对齐契约而不是抄卡面,所以按 #4975 同款探针在现 pin 上重测了两个键的四向(外加array,因为'object'臂刻意不收数组):element:text.content(ElementTextPropsSchema)invalid_union)invalid_union)invalid_union)element:button.label(ElementButtonPropsSchema)invalid_union)invalid_union)invalid_union)两个键在 GA 上都是
z.ZodUnion< [ZodString, ZodRecord< ZodString, ZodString >] >,与卡面 rc.6 的答案一致 ——string=OK i18n-map=OK number=no boolean=no仍然成立,前提未变(premise_still_valid: true)。门侧同一次探针(
manifestFromConfigs+validateTree,与page.tsx的kind:'jsx'编译同一条路),改前:两个键的告警逐字复现。行号漂移:卡面写
elements.tsx:109/:319,:109命中(content),label实际在:319(卡面准确);改后因注释各占几行落到:117/:328。record:alert在 GA 上仍没有ComponentPropsMap条目(实测'record:alert' in ComponentPropsMap === false),所以并入那两处的依据仍然是渲染器 + 已落地的发布面声明,不是 spec —— 与 #4975 的记述一致,没有因为换 pin 而改变。改动(三处)
1. 两个标本各一行(
packages/components/src/renderers/basic/elements.tsx)各带一段注释写明两条臂的依据(GA pin 上的 spec 联合 + 渲染器的
pickLocalized读点),而不是只写「有两条臂」。机制不动。2. 挂进标本验收面(
apps/console/src/__tests__/component-input-union-specimens.test.ts)新增一个
describe(#3832 那个原样不动,它是那次裁定的验收面,五标本的范围应当保持可读),里面三条测试:可达性、spec 裁决派生的臂集合、以及两个标本各一条正向 + 对照。对照按 #3832 的实证成对配齐:变异③(见下)实测到只撤机制时正向断言会空绿,红的只有对照。另外每个标本的臂集合不是重述
['string','object'],而是从 spec 自己的裁决派生(COARSE_ARM_PROBES逐值safeParse,取通过的那些粗类型)—— 这是text-input-inputs-spec-parity.test.ts为第五标本采用的处置:#3832 关掉的是表达力那一半,「臂是否与契约相符」今天没有门禁(finding #4971),per-key 派生是替代品。派生本身也带一条守卫测试(全拒 →[]会让两条比对空洞地相等)。3. PM 并入(
packages/plugin-detail/src/renderers/record-alert.tsx)未导出的局部
RecordAlertProps两处(properties.*+ flat 兼容)的title/body从string放宽。不新增导出,仍是局部。选
string | I18nLabel(import type { I18nLabel } from '@objectstack/spec/ui')而不是手写string | Record< string, string >,两个理由:packages/types/src/base.ts的BaseSchema.label/.description承载的就是这个事实,拼法逐字是string | I18nLabel,import 自@objectstack/spec/ui。新起一种拼法只会让同一件事在仓里有两种写法。I18nLabel=z.input< typeof I18nLabelSchema >,spec 若改动内联形态,这里跟着动;手写Record< string, string >是一份会漂的副本 —— 正是scripts/check-spec-symbol-derivation.mjs那条规则的形状(它按名字匹配,RecordAlertProps不在其射程内,所以这里靠纪律而不是门禁)。注释里同时写明不是 KEYED 那套词表({ key, defaultValue },resolveKeyedI18nLabel),因为本文件的读点是pickLocalized,收的是内联映射 —— Track the@objectstackfamily at17.0.0-rc.6— the adaptation surface beyond what objectstack#7100 could carry #4167 那对结构可混淆形状的老坑。plugin-detail已经依赖@objectstack/spec(^17.0.0),import 不新增依赖。序列化面复核(实测,不是引述折叠约束)
把
manifestFromConfigs(getPublicConfigs())在改前/改后各 dump 一份逐字比对(与dev/manifest-dump.tsx同一对函数):即单臂折叠约束在实测上成立:其余 55 个公开块一个字节没动,数组只出现在真正声明了联合的 7 个键上。
测试
消费半径按「谁读
ComponentInput.type/ManifestInput.type」扫,不按包猜:manifestFromConfigs/validateTree/generateDts的调用点共 24 个文件(packages/sdui-parser、packages/components、packages/layout、packages/plugin-charts、apps/console、examples/schema-catalog、scripts/),全部覆盖在上面的批次里。另外扫了两个块在设计器面的 fixture(app-shell的previews/block-config.ts:234/:315与其测试):那里是kind: 'text'的另一套词表,与注册的inputs无连接 —— 与 #4975 第 13 行的实测结论一致,无需跟改。三条变异记录(先书面预判,后跑;变异前已 commit,还原用
git checkout --,⛔ stash)① 撤回
element:text.content的'object'臂(留机制)。预判 —— 与卡面派发模板预设的方向不同,如实先写下:派发卡预判的是「该标本对照面红」,但那是 #3832 变异①的方向(撤的是任一臂逻辑,数组
type掉进旧switch的default: return null,什么都不产出 → 正向空绿、对照红)。本变异撤的是声明的一条臂、机制完好,单臂'string'照旧产出诊断,所以动的应该是正向那一半:臂集合比对红(['string']vs spec 派生的['string','object']),i18n-map 正向断言红;对照仍然绿(42 /['Account']照旧被报告),element:button与派生守卫也绿。实测:
Tests 1 failed | 9 passed,唯一红的是element:text.content这条,失败点AssertionError: expected [ 'string' ] to deeply equal [ 'object', 'string' ]—— 即臂集合比对(它是该 test 的第一条 expect,vitest 在此停下,所以 i18n-map 正向那条没跑到)。方向与我的预判一致,与卡面模板的预设相反,照直记录。② 给
element:button.label加一条 spec 不接受的假臂('number')。预判:#3832 变异③把这件事分成两半 —— 「联合不是放行一切」那半成立,「臂是否对齐契约」那半有 per-block spec 断言的键会红、没有的会全绿(③b 在
page:card上是 93 files / 856 tests 全绿)。本卡两标本的臂集合是从 spec 裁决派生的,所以这一半应当红:['number','object','string']vs['object','string'];正向与对照都绿(true/['Save']仍被三条臂一致拒绝)。实测:
Tests 1 failed | 9 passed,AssertionError: expected [ 'number', 'object', 'string' ] to deeply equal [ 'object', 'string' ]。预判命中,并且这正是 #3832 ③b 留在绿里的那半 —— 本卡两个标本在本地把它闭上了(全局那半仍是 finding #4971)。③ 把机制撤回 #3832 之前的形状(
checkType对数组type走default: return null),验证对照断言承重。预判:全部七个标本的正向断言空绿、臂集合比对绿(它读的是 manifest 声明而不是门的行为),红的只有对照,报
expected [] to include 'type-mismatch'—— 空数组,即什么都没产出。实测:
Tests 6 failed | 4 passed,六条标本测试全红,失败点逐个落在对照行::151(page:header)、:169(page:card)、:189(record:alert)、:211(text_input)、:295(element:text.content)、:314(element:button.label),报的都是AssertionError: expected [] to include 'type-mismatch'。预判命中,本卡两标本的对照与 #3832 五标本的对照承重方式相同。changeset
.changeset/element-text-button-i18n-arms-4970.md,@object-ui/components+@object-ui/plugin-detail均 minor,理由与 #3832 先例同档:这是发布面扩展而非破坏 —— 单字符串写法保持合法、其余 55 个公开块序列化字节不变、诊断行为只在两个键的合法写入上从「报」变成「不报」。按 AGENTS.md「objectui 不声明major、fixed 组联动」的规则,扩展记 minor(⛔ major)。plugin-detail那一处虽是未导出局部类型、没有发布面移动,但它是packages/plugin-detail/src/**的真实源码改动,按check-changeset-presence.mjs的前提一并列出并在正文里说明其性质(#4975 把只改注释的packages/layout排除在外,这一处比那个重一档)。顺手发现的相邻缺陷(立卡,不并入本 PR)
action.labelis the third narrow slot in the same local props type — renderer resolves an inline locale map, the type saysstring#4998[finding]— 同一个RecordAlertProps里第三处同形槽位:CTA 的action.label两处仍写label?: string,而:128是pickLocalized(props.action?.label, language)。刻意没有顺手改:title/body的第二条臂有已落地的发布面声明可以对齐,而action的发布面是type: 'object'、成员形状只在 description 的散文里,窄声明的唯一对照物是渲染器本身 —— 判据不同,且要在发布面表达它得先有成员形状(PR fix(plugin-detail): record:highlights 的 fields 声明补上 readonly,让 manifest 能被作者读到 (#3407) #3795 的 open question)。observation-class(interface 未导出、pickLocalized收unknown,今天没有消费者被误导)。草稿状态,等 PM 验收后再 undraft。
Generated by Claude Code