Skip to content

fix(studio): the Interfaces rail opens action nav entries instead of disabling them (#4019) - #4880

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-4019-interfaces-rail-action-dataset
Aug 16, 2026
Merged

fix(studio): the Interfaces rail opens action nav entries instead of disabling them (#4019)#4880
yinlianghui merged 1 commit into
mainfrom
claude/issue-4019-interfaces-rail-action-dataset

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #4019

⚠️ 本 PR 只落了卡片的 action 一半。dataset 一半没有实现,也不应该在本 PR 里猜着实现 —— 理由见下面「前提复核」。合并前建议 PM 先把 dataset 拆成独立卡(否则 Fixes 会把一个尚有未决半边的卡片自动关掉)。

前提复核(stale-premise,卡片自己要求先做)

卡片的前提是「两类 preview 已注册,只差 Interfaces pillar rail 接线,与 view/page/dashboard/app/report 并列」。按 origin/main 逐条实测:

前提 实测 依据
两类 preview 已注册 ✅ 属实,而且不止 preview —— 两类还都注册了 default inspector previews/index.tsaction / dataset 两行;inspectors/index.tsActionDefaultInspector / DatasetDefaultInspector
Interfaces rail 是一个「类型列表」,把两类加进去即可 不成立。该 pillar 的 rail 是当前包 App 的 navigation 树:client.list('app')appDraft.navigationNavTreeresolveSurface。view/page/dashboard/report 不是「并列列在 rail 上」,而是被 app 导航引用到才出现 StudioDesignSurface.tsxInterfacesPillar;#2657 的 2026-08-07 审计 §2(a) 早已记录「rails load exactly four types: object / flow / app / permission」
两类「无处可去」 ❌ 两类今天都能在 Metadata Directory 列出/新建/编辑(注册表驱动) 同一份审计 §2(b)

也就是说卡片描述的那个「rail」在 main 上不存在。两个半边因此分道扬镳:

  • action 能落,而且是个真缺陷:NavigationItemSchema action 成员(ActionNavItemSchema),运行时也真的渲染并派发它(NavigationRenderer + useNavActionDispatch,framework#4509)。但 resolveSurface 没有 action 分支,于是同一个条目在设计器里是 disabled 的死条目 —— 产品里能用,设计器里点不开。这就是本 PR。
  • dataset 落不了:NavigationItemSchemaobject | 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.tscenterTab.ts 是同一个既有做法。新增的 action 分支是唯一的行为改动,其余逐行照搬。
  • 只读 actionDef.actionName 这一种拼写。spec 对 action / name / args / input具名拒绝(objectstack#4001),不是第二拼写;实测 17.0.0-rc.6:unrecognized_keys on actionDef + actionName 缺失。这里加 ?? 兜底等于把 examples/schema-catalog: 13 个 grid 示例用了未声明的 cols 键,列数被静默丢弃 —— 要 3/4 列的示例在 docs 站实际渲染成 2 列 #4001 关掉的 bug(条目派发了作者没声明的 action)重新打开 —— 契约优先(Commandment #0.1)。
  • url / separator / component 仍然不可解析,这是对的:站外链接、分隔线、代码内置 UI,都没有可设计的元数据条目。唯独 action 是「有元数据类型却被漏在这一桶里」。
  • 对象级 action 不受影响:ActionNavItemSchema 是严格的 { actionName, params? },没有 objectName,所以导航 action 天然只能是全局 action,这条路径够不着对象级的,也不该够着 —— 它们的家是对象的 Actions 页签(objectui#2330)。
  • InterfacesPillar 导出(DataPillar / AccessPillar 早就是导出的),供 rail 级验收测试挂载。

验收实测

卡片验收 实测
Interfaces pillar 列出 action 条目 改动前后都渲染,但改动前 disabled(40% 透明、点击无效);现在 toBeEnabled() 通过
Interfaces pillar 打开 action 条目的标准编辑面 点击后画布面包屑变为 action · sync_now,ActionPreview 渲染出该 action 的标签
dataset 同上 未实现 —— 见上,需要决策

测试

仓根跑,路径过滤:

pnpm exec vitest run packages/app-shell/src/views/studio-design/ --maxWorkers=2
  Test Files  28 passed (28)
       Tests  155 passed (155)

新增 9 个钉子(navSurface.test.ts 7 + StudioDesignSurface.interfacesAction.test.tsx 2)。fixture 不是靠眼睛判定的:正例直接过 spec 自己的 NavigationItemSchema.safeParse 断言为真,别名反例断言 spec 拒绝设计器同样不解析 —— 免得钉子钉在一个 schema 根本不认的形状上。

turbo run type-check --concurrency=281 successful, 81 totalnode 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)

  • feat(access): localize curated capability labels client-side (#2600 B5 follow-up) #2657 的 2026-07-07 增量裁定:对象级 action 归对象页签,Interfaces 只放全局 action。本 PR 与之一致,且是 spec 强制的(导航 action 结构上就没有 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 无关。
  • Part B 全线 pm:on-hold,本 PR 不触碰;framework/spec 零改动。

Generated by Claude Code

…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>
@github-actions github-actions Bot added the tests label Aug 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 24.7 KB 350 KB
Entry file index-DW9vXhq4.js
Status PASS

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 9.56KB 3.59KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 8.92KB 3.41KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 25.13KB 5.40KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.13KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.64KB 2.21KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 38.46KB 10.17KB
auth (createAuthenticatedFetch.js) 6.34KB 2.43KB
auth (index.js) 2.35KB 1.07KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.02KB 0.88KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 26.07KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.65KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 498.36KB 111.03KB
core (index.js) 3.79KB 1.52KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 157.05KB 43.28KB
fields (index.js) 231.73KB 57.60KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.35KB 1.38KB
i18n (pickLocalized.js) 3.69KB 1.73KB
i18n (provider.js) 23.12KB 7.62KB
i18n (useDisplayLocale.js) 2.84KB 1.45KB
i18n (useObjectLabel.js) 27.59KB 6.63KB
i18n (useSafeTranslation.js) 7.77KB 3.13KB
layout (index.js) 38.86KB 10.83KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.74KB
mobile (index.js) 1.50KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 9.35KB 3.31KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 4.42KB 1.42KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.81KB 0.83KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 46.62KB 12.83KB
plugin-charts (index.js) 64.75KB 18.37KB
plugin-chatbot (index.js) 181.21KB 43.14KB
plugin-dashboard (index.js) 128.14KB 32.58KB
plugin-designer (index.js) 212.39KB 42.83KB
plugin-detail (index.js) 239.81KB 59.97KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 115.34KB 27.74KB
plugin-gantt (index.js) 164.06KB 39.94KB
plugin-grid (index.js) 192.23KB 51.30KB
plugin-kanban (index.js) 52.72KB 14.54KB
plugin-list (index.js) 111.23KB 26.97KB
plugin-map (index.js) 17.91KB 5.72KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 41.70KB 11.19KB
plugin-timeline (index.js) 26.68KB 7.66KB
plugin-tree (index.js) 8.50KB 2.88KB
plugin-view (index.js) 83.81KB 20.49KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 27.53KB 9.41KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.28KB 0.68KB
react (schema-input.js) 1.45KB 0.83KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 4.47KB 2.03KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.87KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-retry.js) 4.32KB 2.02KB
types (index.js) 3.05KB 1.52KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.05KB 1.93KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 3.40KB 1.71KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Copy link
Copy Markdown
Collaborator Author

【PM 验收 · ACCEPT】objectui 分片 PM(session_01GTRjn8xBqp75dk7kFupVRt)对 #4019 action 半的实施验收(dataset 半已拆 #4882 挂决策箱,处置见 #4019 评论):

实物核验:5 文件全在 studio-design/ + changeset(+380/-72),零越包、模型标识 0、releases 0;resolveSurface 抽出为 navSurface.ts 时既有宽容 || 别名分支原样搬运未顺手改(立 #4881 留分诊)且新 case 未复制该形态 —— 边界纪律正确。premise 测量质量高:卡面「rail 接线」框架被证伪(rail 实为 app nav 树、七月矩阵过期),但从中析出 action 是真缺陷(schema 合法 + 运行时可派发,designer 恒 disabled)—— 在 #2657 Part A 裁定精神内修复;07-07「仅全局 action」约束由 ActionNavItemSchema 结构强制;global_nav 撤除时间线(#6888/#4169)不影响本修的考古成立。

反向验证:预判 4 红且 pillar 对失败在 query 侧(~1050ms = findBy 超时)—— 实测逐条命中;两条空绿幸存者主动声明(assert null 对删 limb 同绿,守的是别的性质)而非冒充覆盖。fixture 全部 schema-checked 非目测(正例过 NavigationItemSchema、别名例双重拒绝)。CI 亲读:19 项全 completed(17 success + 2 skipped),零失败;dev 自曝并弃用了无凭据 curl 假报 ALL COMPLETE 的读数,改走认证 MCC 读 —— 与本车道方法论一致,记档。

Fixes #4019 语义已对齐:dataset 半由 #4882 承接,本 PR 合入关卡即 action 半交付。#4881 留分诊。

处置:undraft + auto-merge(SQUASH)。


Generated by Claude Code

@yinlianghui
yinlianghui marked this pull request as ready for review August 16, 2026 23:29
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 16, 2026
Merged via the queue into main with commit 718ca9d Aug 16, 2026
20 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-4019-interfaces-rail-action-dataset branch August 16, 2026 23:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Studio Part A quick wins: wire the already-registered action and dataset previews into the Interfaces pillar rail (#2657 ruling)

1 participant