Uh oh!
There was an error while loading. Please reload this page.
feat(app-shell)!: modal target 只命名 page,退役 object fallback 与前缀约定 (#3925) - #4764
Merged
Merged
Conversation
按维护者裁定(objectstack#6739-A,2026-08-09):`type: 'modal'` 动作的字符串 `target` 只命名 PAGE。`resolveModalTarget` 原先 page-first 之后回落到 object 元数据,并附带 `create_`/`new_`/`add_`/`edit_`/`update_` 前缀约定,把 `create_opportunity` 解析成 object `opportunity` 的 create 表单。 两者一并退役。契约本身从无歧义:spec TSDoc、已发布文档与 `defineStack` 的 交叉引用遍历都规定 modal target 是 page,且该遍历会拒绝 target 非声明 page 的 已注册 modal 动作。回落属于消费端宽容 —— 让运行时供给构建门禁所拒绝的东西, 把编写错误变成"看起来能用",语料也因此学到错误形态。裁定明确拒绝了第三形态 (保前缀、弃裸名)。 非 page 名现在是拒绝,诊断点名 target 并指向 `type: 'form'`。 同 PR 重判 `ModalDescriptor.objectName` 的 "Back-compat" 注记:保留该键,但注记 是错的 —— 它的唯一在产者是 lookup 字段的行内"创建被引用记录",传入的是完整 descriptor,object 身份来自字段的 `referenceTo`,与动作 target 无关。已改写为 descriptor-only 键的说明。 顺带纠正 `resolveModalTarget` 中一段过期 TSDoc:它仍在描述 `null` 会回落到 服务端处理器,而该回落早已由 objectstack#3959 / objectui#3320 移除。 Co-authored-by: Claude <noreply@anthropic.com>
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
This was referenced Aug 16, 2026
yinlianghui
commented
Aug 16, 2026
CollaboratorAuthor
PM 验收(座位 核验记录(实物核验 + CI 亲读):
out-of-scope 已由 PM 落位:#4766(DashboardView 前缀约定第二副本,queue)、#4767(三处诊断文案统一,queue)。 Generated by Claude Code |
yinlianghui
marked this pull request as ready for review
August 16, 2026 04:56
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 16, 2026
akarma-synetal pushed a commit
to akarma-synetal/objectui
that referenced
this pull request
Aug 17, 2026
…jectstack-ai#4780) `type: 'modal'` 动作的字符串 `target` 指不到页面时,三处会拒绝并报错: `useActionModal.modalHandler`(ActionProvider `onModal` 路径)、 `useConsoleActionRuntime.modalActionHandler`(列表页 / SDUI 页 / 声明式动作栏)、 `RecordDetailView.modalActionHandler`(记录页)。三处各自手写文案,于是漂移了。 PR objectstack-ai#4764 退役了 object fallback(modal target 只指页面 —— objectstack#6739 的 维护者裁定),并只改写了 `useActionModal` 一处:点名被拒的 target,指向 `type: 'form'` 这个经校验的「打开对象表单」写法。另外两处 —— 恰恰是 console 用户真正会看到的两处 —— 仍是退役前的旧文案:target「names no page or object to open」,唯一出路只给 `type: 'script'`。「or object」半句描述的是已经不存在的分支, 替代能力则完全没提;最常被读到的两处给出的是最差的建议。 现在三处都从同一个构造器(`utils/modalTargetDiagnostics`)取文案。每个变体都点名 被拒 target 并指向 `type: 'form'`;console 两处额外保留 `type: 'script'` + `params` 提示 —— 它回答的是另一种作者意图(先收集输入再跑 handler),现在与 form 指引并列 而不是取而代之。`modalHandler` 的消息与 PR objectstack-ai#4764 定下的逐字节一致(这是去重,不是 改写),该字节等价性由测试钉住,各调用点确实读同一来源也由测试钉住。 三处改动前后都是硬编码英文的**编写期**诊断:它们点的是元数据键与 spec 类型,读者 是写这个 action 的人。i18n 化是另一张卡的决定;本次只让英文不再自相矛盾。 Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit
to akarma-synetal/objectui
that referenced
this pull request
Aug 17, 2026
…stack-ai#4766) (objectstack-ai#4782) `DashboardView` 自带一份独立的 `onModal` 处理器,不经 `useActionModal`, 因此仓内同时活着两份 `create_`/`new_`/`add_`/`edit_`/`update_` 前缀约定。 维护者裁定(objectstack#6739-A,2026-08-09)判该约定与 object 兜底一并退役, PR objectstack-ai#4764 在 `useActionModal` 侧删干净了 —— 但这条路径够不到这份副本。 这份副本比 useActionModal 那份更宽:它**根本没有 page 解析**。任何字符串 target 都变成 `{ objectName }` 打开对象表单,前缀命中时还先劈成动词 + 对象。 一个真正命名 page 的 target 在这里从来就打不开。 实施前先枚举了注释自称的生产者「server-driven dashboard schemas 发出的 verb_object 名」—— 不存在。两仓语料里没有任何 dashboard 声明过 `header.actions[]`(objectstack `examples/app-{crm,showcase,todo}` 的 3 张 dashboard、`packages/apps/{account,setup,studio}`;objectui `apps/*`、 `examples/*` 含 `schema-catalog` 的 11 张 dashboard),更没有前缀形态的。 该形态在两仓仅存于 objectstack 的校验器与其钉子、以及一行文档示例。 处置不是就地改写而是**交出去**:这个视图不再自己实现契约,改为安装共享的 `useActionModal` —— RecordDetailView 与控制台运行时安装的同一个。于是 dashboard 头部按钮、记录头动作、控制台列表动作三处的解析 / 拒绝 / 报错完全 一致,契约只剩一份实现。被拒 target 的诊断即 PR objectstack-ai#4764 的那一条(点名 target + 指向 `type: 'form'`),因为它现在就是同一段代码。 钉子(`DashboardView.modalTarget.test.tsx`,5 条):前缀名与裸名同判、拒绝 诊断点名并指向 form、page 名正常解析并开出对话框、空 target 的独立诊断、 script 动作路径不回归。拒绝类用例都让被解析的对象**真实存在**于元数据里并 断言 object 元数据从未被问询 —— 对空对象列表断言拒绝会空洞通过。 反向验证(先预判后跑):恢复被删肢体 → 预判 4 红 1 绿,且全部以断言而非超时 转红(refusal 用例加了 `HUNG` 哨兵,把 PR objectstack-ai#4764 记录过的「打开对话框后 promise 永不 settle → 超时并污染邻居」变成快速具名失败)。实测 4 红 1 绿, 最慢 320ms。page 用例转红的是 `getItem('page', …)` 从未被调用那一条断言 —— 它的「对话框出现了」那半在变异下**仍然绿**(旧代码也开对话框,只是开错东西), 这条如实记在用例注释里。 Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#3925
按维护者裁定 objectstack#6739-A(2026-08-09,decision-inbox 轮次「全部接受」):
type: 'modal'动作的字符串target只命名 PAGE。本仓的 page-then-object 解析属于消费端宽容,退役。Base:
2646ccb728e36af1e7b38d79de49c09f041e4ed6改了什么
packages/app-shell/src/hooks/useActionModal.tsx:resolveModalTarget—— 删掉 page 未命中后的 object 元数据回落。非 page 名是拒绝,不再静默解析为 object。create_/new_/add_/edit_/update_前缀约定 —— 随同退役。normalizeModalSchema对字符串一律返回{ targetName },不再拆成动词 + object。裁定明确拒绝了第三形态(保前缀、弃裸名)。modalHandler点名被拒 target 并给出替代:Modal target "contact" names no page — a modal action's `target` names a PAGE, only. To open an object's form, use `type: 'form'` with an `object.view` target.ModalDescriptor.objectName的 "Back-compat" 注记(交付清单第 3 项)—— 重判为「保留,但注记是错的」。理由见下。resolveModalTarget一段过期 TSDoc:它仍写着null会回落到服务端处理器,而该回落早已由 objectstack#3959 / objectui#3320 移除。这段在main上就已与同文件消费者的实际行为矛盾。为什么
objectName保留而不是一并删它不是 back-compat,也不是宽容 —— 它的唯一在产者是 lookup 字段的行内「创建被引用记录」:
@object-ui/fields的LookupField调用execute({ type: 'modal', modal: { objectName, mode } }),传入的是完整 descriptor,object 身份来自字段的referenceTo,而不是从名字猜出来的。这条路径与动作的target无关,因而不在裁定退役范围内。真正退役的是推断:字符串 target 不再能变成objectName。注记已按此改写。风险门(卡内明示:发现真实依赖 → 停手报测量)—— 已枚举,未发现依赖,放行
examples/app-showcaseQuickViewActiontype: 'modal'+target: 'showcase_component_gallery'type: 'form'+showcase_task.editexamples/app-showcase/test/actions.test.tsapps/examples/type: 'modal'动作携带字符串 target(命中项均为 FormView 的type: 'modal',另一个概念)packages/fieldsLookupField.tsx:823{ objectName, mode }descriptor,非字符串 targetpackages/app-shell/.../metadata-admin/package-schema.ts:81FormViewSpec的type: 'modal'(表单呈现模式)测试
packages/app-shell全量:403 文件 / 3831 通过 | 1 skipped。按三类处置逐条重判 fixture,而非批量改写:
getItem从未以('object', …)被调用。useActionModal.test.ts的两条前缀用例改为断言前缀不被解析;新增一条「前缀名与裸名判定完全一致」,直接钉裁定拒绝的第三形态。{ objectName, mode }descriptor 直通(LookupField 路径)、缺失 target 的独立诊断。反向验证 —— 先预判,后跑变异
预判(恢复两条被删肢体后):3 条 normalize 钉转红、2 条拒绝钉转红、诊断钉以超时而非断言转红(因为 target 一旦可解析,
modalHandler会打开对话框并返回一个只在关闭时 settle 的 promise),5 条对照保持绿。实测:7 红 8 绿,比预判多一条。多出来的是诊断钉的邻居「reports a missing target distinctly」—— 单独隔离跑(
-t)在变异下通过,证明它是被上一条超时用例污染的级联失败,不是独立信号。这条不写进报告了事:我据此加固了诊断钉 —— 在调用
modalHandler前先断言resolveModalTarget返回null。这样一旦回落复活,用例会立刻以干净断言失败,而不是挂到超时再把状态泄漏给邻居。type: 'form'对照(交付清单第 4 项)诚实说明:该路径由packages/core的ActionRunner.executeForm承接(导航至/forms/+ 表单名),与useActionModal无任何共享代码,结构上不可能被本变更影响 —— 所以我没有新造一条形式上好看的"form 钉",而是把既有ActionRunner.formObjectIdentity.test.ts+ActionRunner.test.ts作为对照跑通(6 文件 / 159 通过)。门禁
turbo run type-check --concurrency=2→ 81/81 successfulnode scripts/check-control-bytes.mjs→ OK(4277 文件);另按纪律自扫改动文件的控制字节,干净check:action-forward-parity/check:spec-symbols/check:phantom-deps→ 通过eslint(改动文件)→ 0 error(25 条既有anywarning)超范围发现(未立单,按派发词回传 PM)
useConsoleActionRuntime.tsx:600与RecordDetailView.tsx:883都写names no page or object to open,且只指向type:'script',不提type: 'form'。控制台动作的实际用户可见文案出自这两处,而非useActionModal.modalHandler。按边界(views/ 属 [finding] 动作对话框的继承选项谓词只认外层页面的 record:自己在途的 param 值不参与,select 的 dependsOn 门根本到不了 #3765 面)未改。建议单独一张卡统一三处文案。DashboardView.tsx:70-93自带一份独立的前缀约定实现,不经过useActionModal,因此本次退役影响不到它 —— 但这意味着同一个已被裁定退役的约定在仓内仍有第二份活体,注释还自称支持「server-driven dashboard schemas」发出的verb_object。属 views/ 面,未改。