fix(studio): the Interfaces rail opens action nav entries instead of disabling them (#4019) - #4880
Conversation
…f disabling them (#4019) The Interfaces pillar's rail is the package's App `navigation` tree, and each leaf opens the design surface of whatever it binds to. `resolveSurface` bound five shapes — page/object/dashboard/report/view — and not `action`, so an action entry rendered `disabled`: visible in the designer, inert on click. The same entry works in the shipped product (`NavigationRenderer` + `useNavActionDispatch`, framework#4509), and `action` already had a registered preview AND a registered default inspector. Only the binding was missing, so the one nav variant naming an authorable metadata item was the one the designer could not author. - navSurface.ts: `resolveSurface` / `findSurfaceInTree` / `NavNode` / `Surface` extracted out of the 3.7k-line surface so the binding is unit-testable without mounting a pillar — the reason `packageSurfaces.ts` and `centerTab.ts` already live beside it. The new `action` case is the only behaviour change. - `actionDef.actionName` is read as the ONLY spelling. `action` / `name` / `args` / `input` are named REJECTIONS in the spec (objectstack#4001, measured on 17.0.0-rc.6: `unrecognized_keys` + missing `actionName`), not second spellings; a tolerant read would re-open the bug #4001 closed. - `url` / `separator` / `component` stay unresolvable and that is correct — an external link, a divider and a code-shipped UI have no metadata item to design. Object-scoped actions keep the object's Actions tab: `ActionNavItemSchema` is strict `{ actionName, params? }` with no `objectName`, so a nav action is global by construction and this path cannot reach an object-scoped one. - `InterfacesPillar` is exported, as `DataPillar` / `AccessPillar` already are, for the rail-level acceptance test. The `dataset` half of #4019 is NOT here: `NavigationItemSchema` has no `dataset` member, so the Interfaces rail cannot carry one without a decision the card does not make. Reported back to the PM rather than guessed at. Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
|
【PM 验收 · ACCEPT】objectui 分片 PM(session_01GTRjn8xBqp75dk7kFupVRt)对 #4019 action 半的实施验收(dataset 半已拆 #4882 挂决策箱,处置见 #4019 评论): 实物核验:5 文件全在 反向验证:预判 4 红且 pillar 对失败在 query 侧(~1050ms = findBy 超时)—— 实测逐条命中;两条空绿幸存者主动声明(assert null 对删 limb 同绿,守的是别的性质)而非冒充覆盖。fixture 全部 schema-checked 非目测(正例过 NavigationItemSchema、别名例双重拒绝)。CI 亲读:19 项全 completed(17 success + 2 skipped),零失败;dev 自曝并弃用了无凭据 curl 假报 ALL COMPLETE 的读数,改走认证 MCC 读 —— 与本车道方法论一致,记档。
处置:undraft + auto-merge(SQUASH)。 Generated by Claude Code |
Fixes #4019
前提复核(stale-premise,卡片自己要求先做)
卡片的前提是「两类 preview 已注册,只差 Interfaces pillar rail 接线,与 view/page/dashboard/app/report 并列」。按
origin/main逐条实测:previews/index.ts的action/dataset两行;inspectors/index.ts的ActionDefaultInspector/DatasetDefaultInspectornavigation树:client.list('app')→appDraft.navigation→NavTree→resolveSurface。view/page/dashboard/report 不是「并列列在 rail 上」,而是被 app 导航引用到才出现StudioDesignSurface.tsx的InterfacesPillar;#2657 的 2026-08-07 审计 §2(a) 早已记录「rails load exactly four types: object / flow / app / permission」也就是说卡片描述的那个「rail」在 main 上不存在。两个半边因此分道扬镳:
action能落,而且是个真缺陷:NavigationItemSchema里有action成员(ActionNavItemSchema),运行时也真的渲染并派发它(NavigationRenderer+useNavActionDispatch,framework#4509)。但resolveSurface没有action分支,于是同一个条目在设计器里是disabled的死条目 —— 产品里能用,设计器里点不开。这就是本 PR。dataset落不了:NavigationItemSchema是object | dashboard | page | url | report | action | component | separator | group九选一的判别联合,没有dataset成员。dataset 根本无法出现在 app 导航树里,除非改 spec(卡片明令「no framework/spec change required」),或者给 Interfaces pillar 新造一个「非导航的类型列表区」—— 后者是架构决策,不是 quick win。已按硬规停手回报,未写任何投机代码。本 PR 做了什么
resolveSurface增加action分支;导航里的 action 条目变为可点,打开标准设计面(ActionPreview画布 + 已注册的 action inspector),草稿保存走 pillar 其余叶子完全相同的通用路径。navSurface.ts(新):把resolveSurface/findSurfaceInTree/NavNode/Surface从 3729 行的StudioDesignSurface.tsx里抽出来,让这段绑定不用挂载整个 pillar 就能单测 —— 与同目录的packageSurfaces.ts、centerTab.ts是同一个既有做法。新增的action分支是唯一的行为改动,其余逐行照搬。actionDef.actionName这一种拼写。spec 对action/name/args/input是具名拒绝(objectstack#4001),不是第二拼写;实测 17.0.0-rc.6:unrecognized_keysonactionDef+actionName缺失。这里加??兜底等于把 examples/schema-catalog: 13 个 grid 示例用了未声明的cols键,列数被静默丢弃 —— 要 3/4 列的示例在 docs 站实际渲染成 2 列 #4001 关掉的 bug(条目派发了作者没声明的 action)重新打开 —— 契约优先(Commandment #0.1)。url/separator/component仍然不可解析,这是对的:站外链接、分隔线、代码内置 UI,都没有可设计的元数据条目。唯独action是「有元数据类型却被漏在这一桶里」。ActionNavItemSchema是严格的{ actionName, params? },没有objectName,所以导航 action 天然只能是全局 action,这条路径够不着对象级的,也不该够着 —— 它们的家是对象的 Actions 页签(objectui#2330)。InterfacesPillar导出(DataPillar/AccessPillar早就是导出的),供 rail 级验收测试挂载。验收实测
disabled(40% 透明、点击无效);现在toBeEnabled()通过action · sync_now,ActionPreview渲染出该 action 的标签测试
仓根跑,路径过滤:
新增 9 个钉子(
navSurface.test.ts7 +StudioDesignSurface.interfacesAction.test.tsx2)。fixture 不是靠眼睛判定的:正例直接过 spec 自己的NavigationItemSchema.safeParse断言为真,别名反例断言 spec 拒绝且设计器同样不解析 —— 免得钉子钉在一个 schema 根本不认的形状上。turbo run type-check --concurrency=2→ 81 successful, 81 total。node scripts/check-control-bytes.mjs→ OK;改动文件另做了一次grep -naP自查(0 命中)。新增文件 eslint 零告警。反向验证(先预判方向,再跑)
摘掉
case 'action'一条腿。预判:红 4 条 ——navSurface.test.ts的「binds an action nav leaf」「reaches an action leaf nested in a group」,以及两条 pillar 测试(且红在查询而非断言上:叶子解析不出来时 rail 的title回落成node.label,findByTitle('action · sync_now')直接找不到)。实测:4 failed | 5 passed,恰好是点名的那 4 条;两条 pillar 用例各耗时约 1050ms,正是
findBy的 1000ms 超时,确认红在查询上。git checkout还原(未用git stash),复跑 9/9 绿。诚实标注:5 条绿里有 2 条是「因为什么都没产出而绿」,不是覆盖 —— 「no actionName 时不解析」和「只认规范拼写」这两条都断言
null,腿被删掉后一切 action 形状都是null,照样通过。它们防的是「产出一个无名 surface」和「接受 schema 拒绝的拼写」,不防这条腿本身;真正防这条腿的是上面点名的 4 条。与源单裁定的核对(objectstack#6411 / #2657)
objectName)。global_nav」已经作废:global_nav已随 objectstack#6888(protocol 17,维护者 2026-08-09 裁定)移出ACTION_LOCATIONS,objectui#4169 于 2026-08-11 删掉了它的设计器面 —— 都发生在本卡建卡(2026-08-07/08-10)之后。这不影响本 PR:本 PR 接的是「全局 action 的编辑入口」,而全局 action 在运行时的家就是 app 导航本身,与locations无关。pm:on-hold,本 PR 不触碰;framework/spec 零改动。Generated by Claude Code