发现于 cloud 的 pin bump(objectstack-ai/cloud#1017,462b713a → 16fc124a)对 #4667 逐条对账的时候。不影响 cloud 的行为(下面有核算),但 #4667 的裁决依据本身不成立,且留下了跨仓的死代码,所以单独记一条。
packages/spec/src/ui/app.zod.ts 的墓碑文案:
`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`) ...
commit 7d215814 的正文同样以「其 describe() 自己的对冲措辞("if not set, usually defaults to the first navigation item")描述的就是唯一存在的行为」为由,把它归进 authorWarn 死键。
但 objectui 读它
objectstack-ai/objectui@a8ad6c0f,packages/app-shell/src/console/AppContent.tsx:
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);
}
挂在 apps/:appName 的 path="/" 上。它自带的 docblock 把「不是第一项」这件事说得很明确:
Honors the app's explicit `homePageId` (Salesforce-style "Default Landing"); falls back
to the first reachable nav item only when no homePageId is set ... This is what lets the
CRM example open on the Sales Dashboard instead of the Lead list.
findFirstRoute 会跳过 type: 'url' / separator / action,但在导航项之间它就是「第一项」。所以:
- 「no shell ever read it」是错的 —— console 读,而且是唯一决定 app 内落地页的地方;
- 「landing page IS its first navigation item」在没有
homePageId 时成立,有的时候不成立,这正是这个键存在的意义。
(RootLandingRedirect 那一层确实只看 isDefault,墓碑文案的后半句没问题;出问题的是前半句和 app 内部这一层。)
现在的实际状态
spec 17 起 homePageId 是 retiredKey():编译期 never、解析期抛错。于是
- 任何 app 都无法再声明「落地到非第一项」;
findFirstRoute 成为唯一行为。
- objectui 里那段
if (homePageId) 连同 findNavItemById 变成永远进不去的死分支——ADR-0078 要清的那一类,只是这次是渲染器侧。
- docblock 里承诺的「CRM example 落在 Sales Dashboard 而不是 Lead 列表」这条能力,没有替代写法。
cloud 侧的核算(说明危害面,不是说没事)
cloud 唯一的 homePageId 是 CLOUD_APP.homePageId: 'nav_home',而 nav_home 本来就是 navigation 的第一项、第二项是 type: 'url' 的 Open Production 快捷方式(会被 findFirstRoute 跳过),所以删掉键之后 findFirstRoute 解析到同一个 page/welcome,行为不变。cloud#1017 已按这个核算删掉并写了注释。换一个把落地页指向靠后导航项的 app,同一个删除就是静默的行为变更。
要决定的事(不猜,交给维护者)
两条路,代价不同:
倾向 B,理由是:这个键的语义与「导航顺序」重复表达同一件事,两个来源本身就是 #4411 那类陷阱的温床,而「把想先看到的放前面」是作者可以直接做到的;但 A/B 的分歧是产品能力取舍,不该由适配 pin 的人替维护者决定。无论选哪条,app.zod.ts 里「no shell ever read it」这句都要改——它现在会让下一个读者据此做错判断(本次就是先信了这句、再去核渲染器才发现不对)。
关联
发现于 cloud 的 pin bump(objectstack-ai/cloud#1017,
462b713a→16fc124a)对 #4667 逐条对账的时候。不影响 cloud 的行为(下面有核算),但 #4667 的裁决依据本身不成立,且留下了跨仓的死代码,所以单独记一条。#4667 的前提
packages/spec/src/ui/app.zod.ts的墓碑文案:commit
7d215814的正文同样以「其 describe() 自己的对冲措辞("if not set, usually defaults to the first navigation item")描述的就是唯一存在的行为」为由,把它归进authorWarn死键。但 objectui 读它
objectstack-ai/objectui@a8ad6c0f,packages/app-shell/src/console/AppContent.tsx:挂在
apps/:appName的path="/"上。它自带的 docblock 把「不是第一项」这件事说得很明确:findFirstRoute会跳过type: 'url'/separator/action,但在导航项之间它就是「第一项」。所以:homePageId时成立,有的时候不成立,这正是这个键存在的意义。(
RootLandingRedirect那一层确实只看isDefault,墓碑文案的后半句没问题;出问题的是前半句和 app 内部这一层。)现在的实际状态
spec 17 起
homePageId是retiredKey():编译期never、解析期抛错。于是findFirstRoute成为唯一行为。if (homePageId)连同findNavItemById变成永远进不去的死分支——ADR-0078 要清的那一类,只是这次是渲染器侧。cloud 侧的核算(说明危害面,不是说没事)
cloud 唯一的
homePageId是CLOUD_APP.homePageId: 'nav_home',而nav_home本来就是navigation的第一项、第二项是type: 'url'的 Open Production 快捷方式(会被findFirstRoute跳过),所以删掉键之后findFirstRoute解析到同一个page/welcome,行为不变。cloud#1017 已按这个核算删掉并写了注释。换一个把落地页指向靠后导航项的 app,同一个删除就是静默的行为变更。要决定的事(不猜,交给维护者)
两条路,代价不同:
homePageId恢复为 authorable key,并把 objectui 的resolveLandingRoute记进 liveness ledger 的 enforced 一侧(ADR-0049 「有实现就 enforce」)。代价:清空剩余 6 条 authorWarn 死键 —— book ×2 / job.id / translation.validationMessages / app.homePageId / app.areas[].order(ADR-0049,v17 限时) #4667 的 baseline / 墓碑 / conversion 要回退一部分,authorable-surface.json与app-dead-authoring-keys-removed的条目要拆。resolveLandingRoute的homePageId分支与findNavItemById,并明确记录「落地页只能靠导航顺序表达」这条产品约束(含 CRM 示例那句 docblock 要改)。代价:真的失去一个能力,且 objectui 需要同步一次。倾向 B,理由是:这个键的语义与「导航顺序」重复表达同一件事,两个来源本身就是 #4411 那类陷阱的温床,而「把想先看到的放前面」是作者可以直接做到的;但 A/B 的分歧是产品能力取舍,不该由适配 pin 的人替维护者决定。无论选哪条,
app.zod.ts里「no shell ever read it」这句都要改——它现在会让下一个读者据此做错判断(本次就是先信了这句、再去核渲染器才发现不对)。关联
7d215814(退役)