feat(app-shell): approver values become record lookups (framework #3508) - #2834
Conversation
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
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
❌ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
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
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
真机浏览器验证(issue #3508 最后一条验收项)隔离栈:framework showcase 后端 逐类型渲染(审批节点 inspector 实测 DOM)
数据来源(打开 picker 时的实际请求)四种类型均未再打 存值回归设计器里改 team → {"type":"team","value":"team_finance_probe"} // 行 id
{"type":"position","value":"manager"} // 机器名,不是行 id
{"type":"department","value":"bu_acme"} // 行 id
其他
顺带发现(存量问题,非本 PR 引入)在审批人 repeater 里操作任一 React 对同一组件对只告警一次,所以看起来像是新路径引入的。把本 PR 未改动的 即该告警属于 main 上已有的 Generated by Claude Code |
设计器侧落地 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):usersys_user,picker: 'search'→ PeoplePicker,带邮箱副标题)teamsys_team)departmentsys_business_unit,非 sys_department)positionsys_position)org_membership_levelmanagerfieldexpressionqueueposition存 name 是故意的:引擎按sys_user_position.position名字路由,机器名跨环境可移植而 id 不行。「永不困住作者」仍然成立:记录 lookup 带铅笔切换的手工输入逃生口(目标记录尚不存在/悬空 id 仍可填);无 adapter 时(离线 preview gallery)整体退回纯文本。这一模式在 metadata-admin 内已有先例 ——
AccessExplainPanel/AssignedUsersSection早已用RecordPickerDialog+useAdapter()查sys_user,也正是 ADR-0072「picker 只提供真正可解析的引用」的落实。其他:
user垫底):绑定具体某个人是可移植性最差的选择(环境迁移、人员离职),全球主流流程平台的最佳实践都是先引导到间接绑定。ReferenceCombobox新增onSelect:picker 类控件没有 blur 可依赖,repeater 传入 set-and-flush 原子提交(与 checkbox/select 单元格一致)。@object-ui/fieldsLookupField:当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)。依赖
需与 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