Skip to content

feat(app-shell): approver values become record lookups (framework #3508) - #2834

Merged
os-zhuang merged 3 commits into
mainfrom
claude/approval-handler-value-lookup-xytdsd
Jul 27, 2026
Merged

feat(app-shell): approver values become record lookups (framework #3508)#2834
os-zhuang merged 3 commits into
mainfrom
claude/approval-handler-value-lookup-xytdsd

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

设计器侧落地 framework #3508(spec/引擎侧在 objectstack#3536)。

问题

审批节点 Approvers → Value 是个 datalist 组合框,user/team/department/position 的候选接在 client.list(kind)GET /api/v1/meta/:kind(元数据注册表)上。但这些处理人是 sys_user/sys_team/sys_business_unit/sys_position 里的数据记录,注册表列不出来 —— 候选恒空,控件退化成纯文本框,作者只能手敲 userid。

改动

按「谁在背后支撑这个值」分流(KIND_TO_RECORD_LOOKUP,镜像 spec 的 APPROVER_VALUE_BINDINGS):

Type 控件 存值
user 记录 lookup(sys_user,picker: 'search' → PeoplePicker,带邮箱副标题) id
team 记录 lookup(sys_team) id
department 记录 lookup(sys_business_unit, sys_department) id
position 记录 lookup(sys_position) name(机器名)
org_membership_level 严格 Select(owner/admin/member) 枚举
manager 禁用单元格 +「由提交人上级自动解析」
field 维持对象字段选择器 字段名
expression #3447 的 CEL 表达式输入(合并 main 后保留) CEL
queue 从 Type 下拉移除;存量行保留可编辑 + 「运行时未支持」警告

position 存 name 是故意的:引擎按 sys_user_position.position 名字路由,机器名跨环境可移植而 id 不行。

「永不困住作者」仍然成立:记录 lookup 带铅笔切换的手工输入逃生口(目标记录尚不存在/悬空 id 仍可填);无 adapter 时(离线 preview gallery)整体退回纯文本。这一模式在 metadata-admin 内已有先例 —— AccessExplainPanel / AssignedUsersSection 早已用 RecordPickerDialog + useAdapter()sys_user,也正是 ADR-0072「picker 只提供真正可解析的引用」的落实。

其他:

  • Type 下拉顺序改为间接绑定优先(manager/position/department/team/field…,user 垫底):绑定具体某个人是可移植性最差的选择(环境迁移、人员离职),全球主流流程平台的最佳实践都是先引导到间接绑定。
  • ReferenceCombobox 新增 onSelect:picker 类控件没有 blur 可依赖,repeater 传入 set-and-flush 原子提交(与 checkbox/select 单元格一致)。
  • @object-ui/fields LookupField:当 id_field 不是主键时(如 id_field: 'name')改用 $filter 回填显示标签 —— 否则 position 机器名永远 findOne 不到,已存的值显示为空。

测试

  • 新增 FlowReferenceField.lookup.test.tsx(11 例):绑定与引擎语义对齐、各 kind 渲染哪种控件、手工输入逃生口、离线降级且不再打 metadata 端点、枚举严格 select 与脏值仍显示、manager 禁用、queue 警告。
  • 新增 LookupField.idField.test.tsx(2 例):非主键 id_field$filter 回填;默认主键路径仍走 findOne
  • json-schema-to-fields.test.ts 按新 spec 形状更新(xEnumDeprecated 含 queue、xRef map 含 manager)。
  • 合并 main(含 #3447 expression approver)后回归:inspectors 目录 30 文件 / 346 用例通过;app-shell 全量 219 文件 / 1828 用例通过;fields 全量 30 文件 / 377 用例通过。

调试记录:新测试一度挂死,根因是测试里 useMetadataClient mock 每次返回新对象,而 useMetadataListOptions 的 effect 以 client 为依赖 → 无限重渲染。已改为稳定对象(产品代码用的是 memoized client,不受影响),vitest.config.mts 零改动。

依赖

需与 objectstack#3536 一起看:那边把 queue 标为不可授权(xEnumDeprecated)、声明 APPROVER_VALUE_BINDINGS。本 PR 的 KIND_TO_RECORD_LOOKUP 暂以本地常量镜像该契约,待 @objectstack/spec ^16 发布带该导出后可直接 import(已在代码注释中标注)。

🤖 Generated with Claude Code

https://claude.ai/code/session_01GzRYYvxLHqzrqG2EceDKFm


Generated by Claude Code

claude added 2 commits July 27, 2026 04:50
The approval node's Approvers → Value cell sourced user/team/department/
position candidates from the METADATA registry (client.list(kind) →
GET /api/v1/meta/:kind), which lists metadata types, not records. Candidates
were always empty and the control degraded to a raw-id text box — the author
had to hand-type user ids.

Route each kind to what actually backs it, mirroring the engine's resolution
semantics (spec APPROVER_VALUE_BINDINGS):

- user / team / department / position → single-select record lookup over
  sys_user / sys_team / sys_business_unit / sys_position via the DataSource
  adapter (KIND_TO_RECORD_LOOKUP). position commits the machine NAME (the
  engine routes sys_user_position by name); the rest commit the row id.
  Precedent: AccessExplainPanel / AssignedUsersSection already pick sys_user
  records inside metadata editing.
- org-membership-level → strict select over the closed owner/admin/member
  enum; a stored out-of-enum value renders flagged rather than blanked (free
  text is how 'sales_manager' got in).
- manager → disabled cell + 'resolved from the submitter's manager'.
- queue → dropped from the type options (runtime never resolves it) and
  stored rows warn; the value stays editable.

Never-trap-the-author holds: the lookup keeps a manual-entry toggle, and with
no adapter (offline preview gallery) every cell falls back to free text.

Also orders the approver Type options indirect-first (manager/position/…,
user last): binding a named person is the least portable choice.

fields: LookupField hydrates its label through id_field when that is not the
primary id, so a position machine name resolves instead of showing empty.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GzRYYvxLHqzrqG2EceDKFm
# Conflicts:
#	packages/app-shell/src/views/metadata-admin/inspectors/FlowObjectListField.tsx
#	packages/app-shell/src/views/metadata-admin/inspectors/flow-node-config.ts
@vercel

vercel Bot commented Jul 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectui Ignored Ignored Jul 27, 2026 5:06am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

❌ Console Performance Budget

Metric Value Budget
Main entry (gzip) ** KB** KB
Entry file ``
Status FAIL

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 8.20KB 2.97KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 7.57KB 2.97KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 21.70KB 4.21KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.12KB 3.41KB
auth (LoginForm.js) 17.86KB 5.29KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.43KB 2.09KB
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) 33.74KB 8.53KB
auth (createAuthenticatedFetch.js) 4.37KB 1.69KB
auth (index.js) 1.83KB 0.79KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 4.86KB 0.85KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 18.38KB 4.49KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 3.65KB 1.42KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.25KB 0.53KB
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) 450.70KB 98.15KB
core (index.js) 1.86KB 0.63KB
create-plugin (index.js) 9.28KB 2.98KB
data-objectstack (index.js) 127.29KB 31.96KB
fields (index.js) 214.61KB 52.68KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 2.46KB 0.96KB
i18n (pickLocalized.js) 1.70KB 0.83KB
i18n (provider.js) 5.37KB 1.72KB
i18n (useObjectLabel.js) 25.17KB 5.80KB
i18n (useSafeTranslation.js) 2.87KB 1.28KB
layout (index.js) 38.45KB 10.67KB
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) 4.42KB 1.27KB
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) 1.77KB 0.77KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 6.84KB 2.42KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.67KB 1.12KB
permissions (evaluator.js) 4.00KB 1.23KB
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.55KB 0.71KB
plugin-ai (index.js) 15.71KB 3.79KB
plugin-calendar (index.js) 45.37KB 12.48KB
plugin-charts (index.js) 46.90KB 13.26KB
plugin-chatbot (index.js) 179.53KB 42.79KB
plugin-dashboard (index.js) 108.71KB 28.00KB
plugin-designer (index.js) 210.92KB 42.69KB
plugin-detail (index.js) 214.78KB 52.42KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 103.47KB 25.10KB
plugin-gantt (index.js) 162.33KB 39.53KB
plugin-grid (index.js) 176.51KB 46.38KB
plugin-kanban (index.js) 47.82KB 13.18KB
plugin-list (index.js) 98.71KB 23.32KB
plugin-map (index.js) 16.80KB 5.24KB
plugin-markdown (index.js) 13.65KB 4.67KB
plugin-report (index.js) 37.07KB 9.81KB
plugin-timeline (index.js) 25.37KB 7.20KB
plugin-tree (index.js) 8.36KB 2.81KB
plugin-view (index.js) 85.70KB 20.87KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.55KB 0.67KB
providers (UploadProvider.js) 11.71KB 3.53KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 3.19KB 1.38KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 18.70KB 6.09KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.00KB 0.55KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 2.16KB 0.94KB
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 (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) 0.77KB 0.41KB
types (disclosure.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (index.js) 1.97KB 0.93KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 0.20KB 0.18KB
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.04KB 1.93KB
types (system-fields.js) 2.39KB 1.17KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 0.75KB 0.46KB
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

The app-shell build (tsc) failed on the approver record-lookup cell: its
field literal was checked against the wrong FieldMetadata union member, and
`display_field` / `id_field` / `allow_create` were not declared on
LookupFieldMetadata at all — LookupField reads them at runtime, so they were
a de-facto contract no consumer could express type-safely.

Declare them where the renderer reads them (fixing the contract, not casting
around it), and give the approver cell's literal its `type: 'lookup'`
discriminator so it resolves to that member. `id_field` carries the #3508
rationale: an approval `position` approver commits sys_position.name because
the engine routes by machine name.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GzRYYvxLHqzrqG2EceDKFm
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

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

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 8.20KB 2.97KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 7.57KB 2.97KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 21.70KB 4.21KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.12KB 3.41KB
auth (LoginForm.js) 17.86KB 5.29KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.43KB 2.09KB
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) 33.74KB 8.53KB
auth (createAuthenticatedFetch.js) 4.37KB 1.69KB
auth (index.js) 1.83KB 0.79KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 4.86KB 0.85KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 18.38KB 4.49KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 3.65KB 1.42KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.25KB 0.53KB
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) 450.70KB 98.15KB
core (index.js) 1.86KB 0.63KB
create-plugin (index.js) 9.28KB 2.98KB
data-objectstack (index.js) 127.29KB 31.96KB
fields (index.js) 214.61KB 52.68KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 2.46KB 0.96KB
i18n (pickLocalized.js) 1.70KB 0.83KB
i18n (provider.js) 5.37KB 1.72KB
i18n (useObjectLabel.js) 25.17KB 5.80KB
i18n (useSafeTranslation.js) 2.87KB 1.28KB
layout (index.js) 38.45KB 10.67KB
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) 4.42KB 1.27KB
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) 1.77KB 0.77KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 6.84KB 2.42KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.67KB 1.12KB
permissions (evaluator.js) 4.00KB 1.23KB
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.55KB 0.71KB
plugin-ai (index.js) 15.71KB 3.79KB
plugin-calendar (index.js) 45.37KB 12.48KB
plugin-charts (index.js) 46.90KB 13.26KB
plugin-chatbot (index.js) 179.53KB 42.79KB
plugin-dashboard (index.js) 108.55KB 27.96KB
plugin-designer (index.js) 210.92KB 42.69KB
plugin-detail (index.js) 214.78KB 52.42KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 103.47KB 25.10KB
plugin-gantt (index.js) 162.33KB 39.53KB
plugin-grid (index.js) 176.51KB 46.38KB
plugin-kanban (index.js) 47.82KB 13.18KB
plugin-list (index.js) 98.71KB 23.32KB
plugin-map (index.js) 16.80KB 5.24KB
plugin-markdown (index.js) 13.65KB 4.67KB
plugin-report (index.js) 37.07KB 9.81KB
plugin-timeline (index.js) 25.37KB 7.20KB
plugin-tree (index.js) 8.36KB 2.81KB
plugin-view (index.js) 85.70KB 20.87KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.55KB 0.67KB
providers (UploadProvider.js) 11.71KB 3.53KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 3.19KB 1.38KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 18.70KB 6.09KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.00KB 0.55KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 2.16KB 0.94KB
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 (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) 0.77KB 0.41KB
types (disclosure.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (index.js) 1.97KB 0.93KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 0.20KB 0.18KB
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.04KB 1.93KB
types (system-fields.js) 2.39KB 1.17KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 0.75KB 0.46KB
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
Contributor Author

真机浏览器验证(issue #3508 最后一条验收项)

隔离栈:framework showcase 后端 :3787(--seed-admin,持久 sqlite)+ 本分支 objectui console dev(Vite HMR,:5191,DEV_PROXY_TARGET/VITE_SERVER_URL 指向 :3787)。目录数据用真实记录:sys_user 3 条、sys_team 2 条、sys_business_unit 5 条、sys_position 12 条。探针 flow approver_probe 的审批节点带每类型一行审批人,含 queue / role 两行存量脏数据。

逐类型渲染(审批节点 inspector 实测 DOM)

Type Value 控件 实测
user 记录 lookup(PeoplePicker) 头像 + 姓名 + 邮箱副标题,解析出 Dev Admin
team 记录 lookup 解析出 Sales Team
department 记录 lookup 解析出 Acme Corporation(sys_business_unit)
position 记录 lookup 存值是机器名 contributor,显示为 label Contributor —— 即 LookupField 非主键 id_field$filter 回填那一处修复
org_membership_level 严格 Select 显示 Admin
manager 禁用单元格 “Resolved at runtime from the submitter's manager — no value needed.”
field 对象字段 datalist owner
queue(存量) free text + 警示 “Queue approvers are not supported by the runtime yet — this slot resolves to nobody.”
role(存量) 严格 Select sales_manager (invalid) —— 脏值仍显示且被标记

数据来源(打开 picker 时的实际请求)

user        → GET /api/v1/data/sys_user
team        → GET /api/v1/data/sys_team
department  → GET /api/v1/data/sys_business_unit
position    → GET /api/v1/data/sys_position

四种类型均未再打 /api/v1/meta/:type(注册表),#3508 的空候选根因消失。

存值回归

设计器里改 team → Finance Team、position → Project Manager,Publish 后 GET /api/v1/meta/flow/approver_probe:

{"type":"team","value":"team_finance_probe"}      // 行 id
{"type":"position","value":"manager"}             // 机器名,不是行 id
{"type":"department","value":"bu_acme"}           // 行 id

position 存机器名、其余存 id,与引擎语义一致;queue/role 存量行原样往返。

其他

  • Type 下拉实际提供:User / Org Membership Level / Position / Team / Department / Manager / Field / Expression —— 不含 queue、不含 role(来自 objectstack#3536 的 xEnumDeprecated,说明框架侧契约已生效)。
  • 铅笔逃生口:切到手工输入后显示原始存值(如 bu_acme)且可编辑,旁边有放大镜切回 picker。

顺带发现(存量问题,非本 PR 引入)

在审批人 repeater 里操作任一 commitCell 型控件会打一条 React 告警 Cannot update a component (MetadataResourceEditPageImpl) while rendering a different component (FlowObjectListField)。根因是 FlowObjectListFieldcommitCellflush()(→ 父组件 setState)放在 setRows 的 updater 里执行,而 updater 跑在 render 阶段。

React 对同一组件对只告警一次,所以看起来像是新路径引入的。把本 PR 未改动Resolve As(普通 enum select cell)放在第一步操作,告警由它认领,随后的记录 lookup 选取则干净:

⚠️  WARNS  PRE-EXISTING plain select cell (Resolve As)
    clean  NEW record-lookup pick

即该告警属于 main 上已有的 commitCell 写法(checkbox / select / removeRow 都走它),本 PR 只是多了一个调用方。建议单独开一个小 PR 把 flush 移出 updater,不在本 PR 处理。


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review July 27, 2026 06:22
@os-zhuang
os-zhuang merged commit 4b1ed7d into main Jul 27, 2026
14 checks passed
@os-zhuang
os-zhuang deleted the claude/approval-handler-value-lookup-xytdsd branch July 27, 2026 06:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants