Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

spec 17.0.0 正式版会退役 app.homePageId,但 objectui 的 AppContent 正在读它 —— 升级前需要拍板 #3287

Closed

Description

@xuyushun441-sys

在做 objectui#3275(previews 只读 spec 声明的键)时顺带发现并记录(Prime Directive #10),不在那个 PR 里修 —— 它超出 packages/app-shell/.../previews/ 的 scope fence,而且需要维护者对「谁说了算」拍板。

现状:两个版本的 spec 说法相反

objectui 当前 pin 的是 @objectstack/spec@^17.0.0-rc.1(pnpm-lock.yaml 解析到 17.0.0-rc.1,也是 npm 上最新的已发布版本)。在那个已发布的构建里:

node_modules/@objectstack/spec/dist/app.zod-DBPUrAFz.d.ts:764
    homePageId: z.ZodOptional< z.ZodString >;

即 homePageId 是 AppSchema 上一个合法的可选键。实测 ObjectStackSchema.safeParse() 带 homePageId: 'home' 的 app —— PASS。

但 objectstack main(尚未发布,packages/spec 里 version 字串仍是 17.0.0-rc.1)已经用 #4667 / #4680(ADR-0049 enforce-or-remove)把它退役成 retiredKey() 墓碑了。墓碑文案:

app.homePageId was removed in @objectstack/spec 17.0.0 (#4667, ADR-0049) — no shell ever read it. An app's landing page IS its first navigation item (by order), and the root landing follows isDefault routing.

问题:「no shell ever read it」与本仓的代码不符

packages/app-shell/src/console/AppContent.tsx 第 870-885 行的 resolveLandingRoute() 确实在读:

function resolveLandingRoute(activeApp: any, ctx?: NavTemplateContext): string {
  const homePageId: string | undefined = activeApp?.homePageId;
  const navigation = activeApp?.navigation || [];
  if (homePageId) {
    const item = findNavItemById(navigation, homePageId);
    const route = buildItemRoute(item, ctx);
    if (route) return route;
  }
  return findFirstRoute(navigation, ctx);
}

注释写得很明确:「Honors the app's explicit homePageId (Salesforce-style "Default Landing"); falls back to the first reachable nav item only when no homePageId is set or it points at something that doesn't yield a route. This is what lets the CRM example open on the Sales Dashboard instead of the Lead list.」

也就是说墓碑的前提(「从来没有 shell 读过它」)在 objectui 这边不成立 —— 这个键是有真实消费者的,而且它的行为(钉住落地页)与替代方案(取 navigation 的第一项)语义不同:退役后,一个原本落在 Sales Dashboard 的 app 会改为落在 nav 第一项。

影响面(升级到 spec 17.0.0 正式版时)

  1. packages/app-shell/src/console/AppContent.tsx —— resolveLandingRoute() 的整个 homePageId 分支;
  2. packages/types/src/app.ts:415 的 homePageId?: string;;
  3. packages/types/src/__tests__/page-app-dashboard-spec-parity.test.ts:139 把 homePageId 列在期望键里;
  4. apps/console/src/preview-samples.ts:86 的样例 homePageId: 'home'(objectui#3266 刚按当时的 spec 加上);
  5. apps/console/src/__tests__/preview-samples-spec-valid.test.ts:162 现在记的是 ['app', 'landing', 'objectstack#4001 — use homePageId],那条提示届时会指向一个同样已退役的键;
  6. objectui#3275 里 AppPreview 新增的 homePageId 读取(渲染成 nav item 的 id,不是路径);
  7. apps/console/src/components/RootLandingRedirect.tsx 的注释也提到它。

需要拍板的是

A. 上游的裁决是对的,objectui 跟随删除。 那么落地页语义就统一为「nav 第一项(按 order)+ 根落地看 isDefault」,作者要改落地页就重排 navigation。代价:所有显式钉了 homePageId 的 app 落地页会静默改变(这正是 os migrate meta --from 16 要处理的);好处是全平台只有一种落地语义,契约唯一。

B. 墓碑的事实前提有误,应回上游反馈。 因为 objectui —— 官方渲染器 —— 一直在读它,AppContent 的注释还专门解释了它存在的理由(CRM 例子落在 Sales Dashboard)。如果这个能力是想要的,那退役的应该是「不该退」,或者至少墓碑文案不该断言「no shell ever read it」。

倾向 B 先行:先把 AppContent 的实际读取反馈给 objectstack,确认 #4667 的 liveness 判定是不是漏扫了 objectui 这个消费者(它不在 objectstack 仓内,跨仓审计最容易漏的就是这种)。如果确认「就是要删」,再按 A 一次性改完上面 7 处 + 写迁移说明。在拍板之前不要单独动其中任何一处 —— 半改会造成预览、类型与运行时落地路由三者不一致。

相关

⚠️ 未指派 —— 记录用。这是升级 @objectstack/spec 到 17.0.0 正式版的前置阻塞项,建议在那次升级的 PR 里一并处理。


Generated by Claude Code

Activity

  1. xuyushun441-sys commented on Aug 3, 2026

    @xuyushun441-sys
    ContributorAuthor

    维护者裁决(2026-08-03)

    采纳 B 先行:已在 objectstack#4865 主张撤销退役(墓碑前提「no shell ever read it」被本仓证伪;导航排序与落地入口是两个关注点,派生规则表达不了「菜单第一项 ≠ 落地页」)。

    追加裁决:无论上游怎么裁,都在 v17 内一次性了结——维护者要求协议变更集中在 v17 大版本,客户元数据项目一次升级处理完。

    拍板前 7 处仍一处不动。摘 needs-user-decision;本单挂 pin-bump PR 清单,不进派发队列。


    Generated by Claude Code


    Generated by Claude Code

  2. xuyushun441-sys commented on Aug 3, 2026

    @xuyushun441-sys
    ContributorAuthor

    终裁落地(2026-08-03)——路径 A 激活

    objectstack#4865 已由维护者终裁:确定退役。落地语义统一为「nav 首项(按 order)+ isDefault 根落地」。

    执行(不在本单单独做,随 pin-bump PR):

    在 rc.2 pin-bump 之前 7 处仍一处不动——rc.1 下 homePageId 仍合法,提前删会造成预览、类型与运行时落地路由三方不一致。


    Generated by Claude Code


    Generated by Claude Code

  3. xuyushun441-sys commented on Aug 3, 2026

    @xuyushun441-sys
    ContributorAuthor

    路径 A 执行中 —— 触发条件达成

    会话 session_01NVPjPzmmAJ2Ngtvgg5MSRa,分支 claude/issue-3235-spec-rc2-pin-bump(随 #3235 pin-bump PR,本单不单独开 PR)。

    @objectstack/spec@17.0.0-rc.2 已上 npm。实测该版本里 homePageId 已落成墓碑:

    src/ui/app.zod.ts:1151    homePageId: retiredKey(HOME_PAGE_ID_RETIRED),
    

    并且墓碑文案已按 objectstack#4895 更正 —— 不再宣称「no shell ever read it」,改为写明前提修正(src/ui/app.zod.ts:976 起:「premise corrected in …」,并指向本仓 AppContent 这个真实消费者)。上游把事实认回来了,退役结论不变。

    正文 7 处按裁决一并删除,变更集写明落地页语义变更(显式钉了 homePageId 的 app 将改落在 nav 首项)并指向 os migrate meta --from 16。


    Generated by Claude Code


    Generated by Claude Code

  4. xuyushun441-sys commented on Aug 3, 2026

    @xuyushun441-sys
    ContributorAuthor

    更正:本单清单不完整,以 #3264 为准

    派发后发现 #3264 与本单是同一件事(该单早两小时立,由另一个 agent 记录),且清理范围比本单正文的 7 处更全,本单漏了:

    • findNavItemById()(AppContent.tsx:855)—— 删掉 homePageId 分支后它失去唯一调用者,必须一并删;
    • :840 那条「used when homePageId resolved to an exact match」的注释。

    已补发给实施 agent,以 #3264 正文为清理准绳。两单由同一个 PR 一并 close,#3264 不关为 duplicate —— 它是本单的超集。

    本单的独有价值仍在:跨仓消费点清单(含 preview 样例、守卫提示文案、RootLandingRedirect 注释这些 #3264 未列的外围点)与维护者终裁记录。两张单合起来才是完整范围,这本身就是「一次审计只看得见自己那一侧」的又一个样本。


    Generated by Claude Code


    Generated by Claude Code

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

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions