在做 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 正式版时)
packages/app-shell/src/console/AppContent.tsx —— resolveLandingRoute() 的整个 homePageId 分支;
packages/types/src/app.ts:415 的 homePageId?: string;;
packages/types/src/__tests__/page-app-dashboard-spec-parity.test.ts:139 把 homePageId 列在期望键里;
apps/console/src/preview-samples.ts:86 的样例 homePageId: 'home'(objectui#3266 刚按当时的 spec 加上);
apps/console/src/__tests__/preview-samples-spec-valid.test.ts:162 现在记的是 ['app', 'landing', 'objectstack#4001 — use homePageId],那条提示届时会指向一个同样已退役的键;
- objectui#3275 里
AppPreview 新增的 homePageId 读取(渲染成 nav item 的 id,不是路径);
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
在做 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 上最新的已发布版本)。在那个已发布的构建里:即
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()墓碑了。墓碑文案:问题:「no shell ever read it」与本仓的代码不符
packages/app-shell/src/console/AppContent.tsx第 870-885 行的resolveLandingRoute()确实在读:注释写得很明确:「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 正式版时)
packages/app-shell/src/console/AppContent.tsx——resolveLandingRoute()的整个homePageId分支;packages/types/src/app.ts:415的homePageId?: string;;packages/types/src/__tests__/page-app-dashboard-spec-parity.test.ts:139把homePageId列在期望键里;apps/console/src/preview-samples.ts:86的样例homePageId: 'home'(objectui#3266 刚按当时的 spec 加上);apps/console/src/__tests__/preview-samples-spec-valid.test.ts:162现在记的是['app', 'landing', 'objectstack#4001 — usehomePageId],那条提示届时会指向一个同样已退役的键;AppPreview新增的homePageId读取(渲染成 nav item 的 id,不是路径);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 处 + 写迁移说明。在拍板之前不要单独动其中任何一处 —— 半改会造成预览、类型与运行时落地路由三者不一致。相关
homePageId)landing,当时的替代就是homePageId)、objectstack#4667 / #4680(现在把homePageId也退役)@objectstack/spec到 17.0.0 正式版的前置阻塞项,建议在那次升级的 PR 里一并处理。Generated by Claude Code