Skip to content

finding(docs): core/app-schema.mdx's "Global Actions" snippet imports the wrong same-named MenuItem #6692

Description

@os-sales

Found while implementing #6523 / #6346 (the overlay.ts MenuItem discriminated-union +
onClick fix). Filed unassigned and out of both cards' fence — this is a pre-existing,
unrelated docs/type-naming collision, not something either ruling's diff touches.

What was measured, at c75b928e6

content/docs/core/app-schema.mdx's ## Global Actions section has a real, compiled
ts fence (unlike the plaintext "Schema" blocks elsewhere on this same page):

import type { MenuItem } from '@object-ui/types';

interface AppAction {
  ...
  items?: MenuItem[];     // For type='dropdown' or 'user'
  ...
}

import type { MenuItem } from '@object-ui/types' resolves to the overlay MenuItem
(packages/types/src/overlay.ts, re-exported at index.ts:255 under the bare name
MenuItem) — the discriminated union shared by ui:dropdown-menu/ui:context-menu/
ui:menubar, which #6523 just reshaped.

But the real AppAction.items field — packages/types/src/app.ts:728 — is declared
inside app.ts itself, so MenuItem[] there resolves to app.ts's own local MenuItem
(app.ts:461), a structurally different, legacy navigation-item shape (type?: 'item' | 'group' | 'separator', path, href, badge, hidden — none of which the overlay
MenuItem declares). The barrel re-exports app.ts's version under the renamed
AppMenuItem (index.ts:59) specifically to avoid this collision — but the docs snippet
imports the bare, unrenamed name, so it silently picks up the WRONG same-named type.

Confirmed this is not new: app.ts's own MenuItem and overlay.ts's MenuItem have
always been structurally different (this predates #6523's union split — before it, the
overlay MenuItem was still a single object with label: string required, icon,
disabled, onClick, shortcut, children, separator?: boolean, none of which match
app.ts's type/path/href/badge/hidden shape either).

Why this doesn't block #6523/#6346

The snippet is a bare type reference (items?: MenuItem[]), not an object literal that
instantiates the shape — so it compiles under check-doc-snippet-types.mjs regardless of
which MenuItem it actually resolves to, and #6523's union split doesn't change whether it
compiles. Confirmed unaffected by grepping the diff: the snippet's own text is untouched by
either card.

The open question

Two candidate fixes, not adjudicated here:

  • A — the snippet's import is simply wrong; change it to import type { MenuItem as AppMenuItem } from '@object-ui/types'; (or import from '@object-ui/types''s already-exported
    AppMenuItem name directly) so the reader compiles against the type that's actually there.
  • B — AppAction.items itself is questionable: dropdown/user toolbar actions plausibly
    WANT the richer overlay MenuItem (icon, onClick, shortcut, submenu children, divider) more
    than the legacy nav-item shape (path/href/badge) app.ts gives them today — worth
    checking whether header-bar.tsx (the renderer consuming AppAction.items) actually reads
    app.ts's fields or the overlay ones before choosing.

Whichever way, this is a genuine decision (which MenuItem AppAction.items should mean),
not a mechanical fix, so it doesn't qualify for the bounded in-place-fix exemption on the PR
that found it.

Refs: #6523 · #6346 (found while implementing, unrelated to either ruling).

Activity

  1. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Aug 28, 2026
  2. added theissue type on Aug 28, 2026
  3. huangyiirene commented on Aug 28, 2026

    @huangyiirene
    Collaborator

    分诊:入决策箱 + 四棱块

    定级:needs-user-decision · domain:ui · documentation · priority:p2 · type Bug。卡面自己写明「这是一个真正的裁决,不是机械修复」,且 B 改的是已发布类型 ⇒ 命中人工地板(协议/已发布契约变更)。

    <!-- os-decision-facets -->

    一句话问题:文档里 import type { MenuItem } from '@object-ui/types' 静默解析到 overlay 的 MenuItem,而真正的 AppAction.items 用的是 app.ts 自己那个同名但结构不同的类型(barrel 用 AppMenuItem 重命名正是为了避开这次碰撞)。要只修文档,还是顺带回答「AppAction.items 到底该是哪个 MenuItem」。

    选项 × 真实客户可感成本

    做什么 客户可感代价
    A 文档 import 改成 AppMenuItem 零客户可感变化。一行、随时可落。碰撞与「items 该是什么」原样留着
    B 让 AppAction.items 改用 overlay MenuItem(icon/onClick/shortcut/children) 已发布类型变更:今天写 path/href/badge 的 app 元数据当场变红。需先清点消费面

    ⇒ A 与 B 不互斥:A 是止血,B 是治本,先 A 不浪费 B 的任何工作。

    四棱
    ① 长远合理性:一个包里两个同名 MenuItem、靠 barrel 重命名回避 —— 碰撞本身是长期负债。A 留着它,B 才可能消解它。
    ② 业务拉动:客户能感知的只有一件事 —— 用 AppAction 的 dropdown/user 工具栏动作能不能带 icon / onClick / 子菜单。⚠️ 这取决于 header-bar.tsx 今天实际读哪套字段,而没人测过(见置信缺口)。
    ③ 防 AI 犯错 —— 这一维是分水岭。⭐ 这段是真编译的 ts fence,但它是裸类型引用(items?: MenuItem[],不实例化),所以解析到错的类型照样编译通过,check-doc-snippet-types.mjs 抓不到。⇒ 门禁在这里结构性失明,AI 作者照抄就按 overlay 形状构造 items。A 直接删掉这个陷阱。
    ④ 不扩散:A 一行零风险;B 是已发布类型变更,需清点。⛔ 不建议把 B 塞进 A。

    推荐:先 A(止血,随时可落);B 单独立卡,并以一次实测为前置。⛔ 本席不裁 B 的方向。

    ⭐ 置信缺口(卡面自己标了「worth checking」但没做)

    没有人读过 header-bar.tsx 实际消费 AppAction.items 的哪些字段。 这一条决定 B 的性质:

    • 若它读的是 overlay 那套(icon/onClick/children)⇒ B 是修今天就坏的东西;
    • 若它只读 path/href/badge ⇒ B 是新增能力,人工地板判定完全不同。

    ⇒ 建议裁前先补这一测;它可能直接决定 B 该不该存在。

    ⚠️ 路由后果(提前说明,避免以后被当成漏标)

    domain:* 按补丁落地的包定。A 落在 content/docs/** ⇒ domain:ui(现状)。若裁 B,落点移到 packages/types/src/app.ts,按本仓三流规则 domain:* 须改判为 domain:spec。现保留 domain:ui,⛔ 不预判 B。

    低摩擦裁决格式:回「A」/「A + 立 B 卡」/「先测 header-bar」。

    裁后执行段:裁 A ⇒ 本卡转 pm:queue(domain:ui,一行 import 改动)。裁「A + 立 B 卡」⇒ 本卡按 A 执行,B 由 domain:spec 席新立卡并带上那次 header-bar.tsx 实测。裁「先测」⇒ 本卡留决策箱,测量结果回帖后再裁。


    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

No one assigned

    Labels

    documentationImprovements or additions to documentationdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions