发现于 #4003 的实施,不在该 PR 处理 —— 那一单只把 div 转成布局类型并一律写声明键 gap,没有碰既有 stack 节点。
现象
StackSchema 继承自 FlexSchema,声明的间距键只有 gap:
packages/types/src/layout.ts:180-182 —— export interface StackSchema extends Omit<FlexSchema, 'type'> { type: 'stack'; }FlexSchema 的键:direction / justify / align / gap / wrap / children
spacing 不在其中。但渲染器额外读它,还特意用 as any 绕过类型:
packages/components/src/renderers/layout/stack.tsx:25 —— const gap = schema.gap ?? (schema as any).spacing ?? 2;
于是 spacing 成了一个只存在于 consumer 里的第二套事实契约。catalog 里实测 135 个 stack 节点、39 个文件写的是 spacing 而不是 gap(例如 marketing/features-grid.json 的 {"type":"stack","spacing":3}、auth/*.json 全系列、actions/action-toolbar.json)。
与 #4001 的关系:同一个病,另一种表现
#4001 是"未声明的键被静默丢弃"(cols → 列数丢失,渲染成 2 列)。这条是同一个病的宽容变体:未声明的键被consumer 接住了,所以渲染看起来是对的,没人会去查,错的写法就一直传播 —— 而 #4001 正文说过,"碰巧看起来对"的那一类是最坏的一类。
AGENTS.md #0.1 直接点名这个形状:"宽容的 fallback 会把错写法固化成第二套事实契约,稀释 spec,并掩盖 producer 的 bug —— 一套严格契约胜过 N 种方言。"
今天没有用户撞到,但作者面已经被教坏
修法方向(契约优先)
改 producer:135 个节点 spacing → gap,然后删掉 stack.tsx:25 的 (schema as any).spacing ?? 那一截。⛔ 不要反过来把 spacing 补进 StackSchema 来"合法化" —— 那是把一个纯粹多余的别名提升成公共契约,gap 已经在那里,两个名字做同一件事只会让 AI 作者继续猜。
注意:改完之后渲染不变(两条路径本来就归到同一个 gap),所以这是一次纯机械替换,适合和 #4003 之后的 catalog sweep 合并成一张卡。#4003 已经留下 examples/schema-catalog/test/layout-props-conversion.test.tsx 这个位置,禁令加在那里最省。
参考位置
packages/types/src/layout.ts:180-182(StackSchema,无 spacing)packages/components/src/renderers/layout/stack.tsx:25((schema as any).spacing)packages/components/src/renderers/layout/flex.tsx(同族,不读 spacing)packages/components/src/renderers/layout/stack.tsx:98-103(注册 inputs 只有 gap,与渲染器读的键已不一致)examples/schema-catalog/src/schemas/**(135 个命中节点)
关联:#4001 / #4003 / #3965
发现于 #4003 的实施,不在该 PR 处理 —— 那一单只把
div转成布局类型并一律写声明键gap,没有碰既有stack节点。现象
StackSchema继承自FlexSchema,声明的间距键只有gap:packages/types/src/layout.ts:180-182——export interface StackSchema extends Omit<FlexSchema, 'type'> { type: 'stack'; }FlexSchema的键:direction/justify/align/gap/wrap/childrenspacing不在其中。但渲染器额外读它,还特意用as any绕过类型:packages/components/src/renderers/layout/stack.tsx:25——const gap = schema.gap ?? (schema as any).spacing ?? 2;于是
spacing成了一个只存在于 consumer 里的第二套事实契约。catalog 里实测 135 个stack节点、39 个文件写的是spacing而不是gap(例如marketing/features-grid.json的{"type":"stack","spacing":3}、auth/*.json全系列、actions/action-toolbar.json)。与 #4001 的关系:同一个病,另一种表现
#4001 是"未声明的键被静默丢弃"(
cols→ 列数丢失,渲染成 2 列)。这条是同一个病的宽容变体:未声明的键被consumer 接住了,所以渲染看起来是对的,没人会去查,错的写法就一直传播 —— 而 #4001 正文说过,"碰巧看起来对"的那一类是最坏的一类。AGENTS.md #0.1 直接点名这个形状:"宽容的 fallback 会把错写法固化成第二套事实契约,稀释 spec,并掩盖 producer 的 bug —— 一套严格契约胜过 N 种方言。"
今天没有用户撞到,但作者面已经被教坏
stack.tsx接住了),production 构建也正确 —— 没有可见故障,所以按分诊惯例打finding,由分诊定级。spacing;哪天有人把stack换成flex(语义上只差一个direction),flex.tsx不读spacing,间距立刻静默变成默认值 —— 那时才是 examples/schema-catalog: 13 个 grid 示例用了未声明的cols键,列数被静默丢弃 —— 要 3/4 列的示例在 docs 站实际渲染成 2 列 #4001 的完整重演。flex节点当前用spacing的数量是 0(实测),所以这个雷还没被踩到。修法方向(契约优先)
改 producer:135 个节点
spacing→gap,然后删掉stack.tsx:25的(schema as any).spacing ??那一截。⛔ 不要反过来把spacing补进StackSchema来"合法化" —— 那是把一个纯粹多余的别名提升成公共契约,gap已经在那里,两个名字做同一件事只会让 AI 作者继续猜。注意:改完之后渲染不变(两条路径本来就归到同一个
gap),所以这是一次纯机械替换,适合和 #4003 之后的 catalog sweep 合并成一张卡。#4003 已经留下examples/schema-catalog/test/layout-props-conversion.test.tsx这个位置,禁令加在那里最省。参考位置
packages/types/src/layout.ts:180-182(StackSchema,无spacing)packages/components/src/renderers/layout/stack.tsx:25((schema as any).spacing)packages/components/src/renderers/layout/flex.tsx(同族,不读spacing)packages/components/src/renderers/layout/stack.tsx:98-103(注册inputs只有gap,与渲染器读的键已不一致)examples/schema-catalog/src/schemas/**(135 个命中节点)关联:#4001 / #4003 / #3965