Skip to content

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

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 >;

homePageIdAppSchema 上一个合法的可选键。实测 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:415homePageId?: string;;
  3. packages/types/src/__tests__/page-app-dashboard-spec-parity.test.ts:139homePageId 列在期望键里;
  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

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