Skip to content

app-schema-renderer 的 mobileNavMode 声明成自由文本 string,实现却是三值联合 —— 写错的值静默回落,且词表里的 'hamburger' 在渲染器零读点 #3985

Description

@yinlianghui

发现于 #3972 的实施(PR #3984),不在该 PR 处理 —— #3972 的完成范围是"对正确写法报假诊断"那一类(page-header 漏 icon/actions、navigation-renderer.items 类型写错),这一条相反:声明面比实现更宽,于是该报的一条 invalid-enum 从来没报过。而且它的修法需要先定词表,不该由实施 agent 猜。未认领,交 PM triage。

与 #3818 同一失效家族(声明的语义与渲染器实际读点不符),但方向相反:#3818 是发布了 auto|custom 而渲染器只认已退役的旧词表;这里是根本没发布词表,而实现只兑现三值中的一个。

机制

packages/layout/src/index.ts 的 app-schema-renderer 声明:

{ name: 'mobileNavMode', type: 'string' },

而实现是三值联合(packages/layout/src/AppSchemaRenderer.tsx:57):

export type MobileNavMode = 'drawer' | 'bottom_nav' | 'hamburger';

ManifestInputType 有 'enum',ManifestInput 有 enum 数组字段(packages/sdui-parser/src/types.ts:51-73),checkType 对 'enum' 会给出 error 级的 invalid-enum(validate.ts:130-140)—— 所以这个词表是完全表达得了的,和 #3832 的联合类型表达力问题无关。

后果是两条,都无诊断:

  1. 写错值静默回落。 作者(尤其 AI 作者)写 mobileNavMode: "bottom-nav"(连字符,而实现要的是下划线 bottom_nav)拿不到任何诊断:type: 'string' 判的只是 typeof value === 'string',于是通过;渲染器 :538 的 showBottomNav = mobileNavMode === 'bottom_nav' 不成立,静默走 drawer。设计器那边同样把它渲染成自由文本输入框,而不是三选一。
  2. 词表里的 'hamburger' 本身零读点。 全文件对 mobileNavMode 的读只有 :430 的默认值(= 'drawer')与 :538 的 === 'bottom_nav',所以 'hamburger' 与 'drawer' 行为完全一致 —— 而 :411 的 JSDoc 把它写成 "hamburger(collapsed sidebar)",是个已发布却未实装的语义(page:header.icon 与 page:card.actions:spec 声明、渲染器零读点 —— 二择一(接线 / 按 showSubscriptionToggle 先例声明并写明 KNOWN GAP) #3829 / record:details 的 layout 发布了 auto|custom 语义,渲染器唯一的读点只认 spec 已退役的 inline|compact —— auto/custom 从未被实装 #3818 的同一类)。

为什么不在 #3972 的 PR 里顺手改

"声明成 enum"这个动作要求先回答"三个值里哪些是真的",而两条路的公开契约不同:

另外任一条路都会新增 error 级诊断:今天写了词表外值的仓外消费者会从"静默回落"变成"校验报错"。方向上这是对的(declared = enforced,让 AI 作者当场被拦),但它是收紧决定,得有人拍。

可达性诚实标注

仓内没有任何 JSON 元数据把 app-schema-renderer 当 schema 节点写(全仓 grep 只命中注册本身);React 调用侧受 TS 约束,mobileNavMode 传错值编译期就红。所以今天的实际受害者只有仓外按 inputs / packages/layout/README.md 作 schema 驱动的消费者,以及"以为 'hamburger' 有效"的任何调用方(仓内是 0 个:唯一传值处是 __tests__/AppSchemaRenderer.test.tsx:513 传的 'bottom_nav')。因此按 observation-class 打 finding、不挂 pm:queue,定级请分诊时自行判断。

参考位置

  • packages/layout/src/index.ts —— app-schema-renderer 的 mobileNavMode input
  • packages/layout/src/AppSchemaRenderer.tsx:57 —— MobileNavMode 三值联合
  • packages/layout/src/AppSchemaRenderer.tsx:430 / :538 —— 全部两个读点
  • packages/layout/src/AppSchemaRenderer.tsx:411 —— 把 hamburger 写成 collapsed sidebar 的 JSDoc
  • packages/sdui-parser/src/validate.ts:130-140 —— invalid-enum(error 级)

关联:#3972(发现于此实施)、#3984(该单的 PR)、#3818(同族、方向相反)、#3829(声明了却零读点)、#3832(不同根因:ComponentInput.type 表达力)

Activity

  1. claude commented on Aug 9, 2026

    @claude
    Contributor

    Findings triage — re-graded finding → needs-user-decision (labels updated in the same pass).

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. os-zhuang commented on Aug 10, 2026

    @os-zhuang
    Contributor

    Maintainer ruling (2026-08-10, directed in session session_01BPWqbmEFU8gJepBJTHESXd): narrow the declaration; retire the dead value.

    mobileNavMode's declared type becomes the real three-value union — a misspelled value must refuse at validation, not silently fall back. hamburger is retired under enforce-or-remove: zero read points in the renderer means it never was behavior. If someone wants it as behavior, that is an implementation card first.

    needs-user-decision → pm:queue.


    Generated by Claude Code

  3. self-assigned this
    on Aug 17, 2026
  4. yinlianghui commented on Aug 17, 2026

    @yinlianghui
    CollaboratorAuthor

    认领(objectui 车道分片 PM,会话 session_01GTRjn8xBqp75dk7kFupVRt,批次 17):branch claude/issue-3985-mobilenavmode-enum。

    按 2026-08-10 维护者裁定实施:声明收窄为实现了的值的枚举、误拼在校验层拒绝(不再静默回落)、hamburger 按 enforce-or-remove 退休(渲染器零读点,从未是行为;要行为先立实现卡)。文件面:packages/layout/src/index.ts(声明)、packages/app-shell 的 AppSchemaRenderer(联合类型与读点)、相关校验/文档/catalog 触点。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions