feat(core): declare the 18 spec-owned action keys ActionDef absorbed silently (objectstack#4075 第 2 步) - #3190
Merged
Conversation
…silently `ActionDef` ends with `[key: string]: any`, so it accepted any key of any type — a typo (`targt`) and a retired spec key (`execute`) both type-checked, then the runner silently bound no handler (the #2169 shape). Step 1 made that audible with a dev-mode warning; this is step 2, promoting the keys that warning identified as legitimate into real fields. - Promote 18 spec-owned keys to explicit optional fields, every type DERIVED from `@objectstack/spec`'s `ActionInput` rather than hand-copied. A hand-written duplicate of a spec shape is a second contract that drifts, which is the failure this issue is about. - Derive from `z.input`, not `z.infer`: `ActionSchema` is a `ZodPipe` whose transform narrows `visible` to the `{ dialect, source }` envelope alone. This runner reads authored rows unparsed, so it sees the input shape; `Action` would have rejected the raw-string predicate ActionEngine supports. - Delete three `as any` casts in `ActionEngine` (`visible`, `requiredPermissions`, `locations`) that existed only because the fields were undeclared. Package `as any` count drops 9 -> 3. - Mark `@deprecated` only the four dialect keys the runner itself proves are aliases: `actionType` -> `type`, `api`/`endpoint` -> `target`, `navigate` -> flat `target`/`openIn`. The rest are runner mechanics with no spec spelling. `shortcut` and `bulkEnabled` derive to `undefined` because spec 17 retired both as `retiredKey()` tombstones — so authoring either, already a hard parse rejection, is now a compile error too. Hand-copying would have quietly re-legitimized two dead keys. The index signature stays; removing it is step 3, and the inverted pin asserting it is still present remains the issue's completion check. Refs objectstack-ai/objectstack#4075 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012C2cd7tL8QDoZ2QKN3djJ5
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
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 free
to 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.
Refs objectstack-ai/objectstack#4075(第 2 步,不关闭)
分阶段路线的第 2 步。第 1 步(objectui#3032)已经把「未识别键」变成 dev 警告并盘点出清单;本 PR 把清单里合法的那一半变成真正的字段。第 3 步(删除索引签名)仍未做,issue 保持打开。
背景
ActionDef以[key: string]: any结尾,因此任何键、任何类型都能通过编译。错拼(targt)和已退役的 spec 键(execute)都能 type-check,然后 runner 静默地不绑定任何 handler —— 这正是 #2169「Mark Done 什么都不做」的形状。本 PR 做了什么
1. 18 个 spec 拥有、
ActionDef从未声明的键,提升为显式可选字段ai、aria、bodyExtra、bodyShape、bulkEnabled、component、icon、locations、mode、objectName、order、recordIdField、recordIdParam、requiredPermissions、requiresFeature、shortcut、variant、visible。每个类型都从
@objectstack/spec的ActionInput派生(SpecActionInput['locations']等),不手抄。手抄一份 spec 形状等于造出第二份会静默漂移的契约 —— 而这正是本 issue 要解决的失败模式本身。效果:order: 'first'、variant: 'chartreuse'、locations: ['nope']现在是编译错误,此前被索引签名静默吞掉。2. 从
z.input派生,而不是z.infer—— 这一点是承重的,不是风格选择ActionSchema是一个ZodPipe,它的 transform 会收窄作者写下的形状:visible的作者侧是string | { dialect, source },但 infer 之后只剩下 envelope 对象那一支。本 runner 消费的是未经解析回灌的 authored/stored 行(#3903 已确立),看到的是 input 形状。若按既有ActionComponent的先例从Action(z.infer)派生,就会把ActionEngine明确支持、且被ActionEngine.visibility.test.ts钉住的裸字符串谓词判成类型错误。component是纯 enum,两者恰好重合,所以旧先例没有暴露这个差异。3. 删掉
ActionEngine里 3 处as anyvisible、requiredPermissions(位置过滤器)、locations(注册)。这些 cast 只因字段未声明而存在。该文件as any从 9 处降到 3 处,剩下的 3 处与本 issue 无关(ActionContext的开放数据袋、evaluator API),按 issue 要求不动ActionContext的索引签名。4. 只给 4 个 objectui 方言键标
@deprecatedactionType→type;api与endpoint→target(executeAPI本就按api || endpoint || target解析);navigate→ 扁平的target/openIn(executeNavigation本就回退到它们)。刻意没有给其余 13 个方言键加
@deprecated:它们是 runner 自身的机制(chain/chainMode、onSuccess/onFailure、toast、close、reload、redirect、modal、confirm、condition 等),spec 里没有对应拼写,指向一个不存在的替代品比保持现状更糟。一个刻意的破坏性边缘
shortcut和bulkEnabled派生出来是undefined而不是可用类型 —— 这是正确结果。spec 17 把两者退役为retiredKey()墓碑(z.never()),作者写任何一个都是硬解析拒绝。派生(而非手写shortcut?: string)零成本地把这个运行时拒绝提升成了编译错误;手抄则会悄悄把两个死键重新合法化。因此:此前能编译的
shortcut: 'ctrl+k'现在编译不过。这类元数据本就已被平台拒绝,本 PR 只是把失败挪到能被修复的地方。宿主仍可通过ActionEngine.registerAction(action, { shortcut, bulkEnabled })显式传入。changeset 因此标
minor而非patch—— AGENTS.md 的版本策略明确要求 objectui 自身的破坏性变更标 minor(绝不 major),正文里写清 breaking 语义。派发指令写的是 patch,这里按仓库约定取 minor;若维护者认为该边缘不算 breaking,改一个词即可。测试
新增
packages/core/src/actions/__tests__/actionKeys.types.test.ts(30 条)。被验证的性质是「编译器会拒绝错误类型的值」,运行时断言观察不到,而expectTypeOf在本仓不可用(vitest.config.mts未开typecheck模式,开启它属于 objectui#3181 的门禁改动,不该搭本 PR 的车)。所以测试用 TS 编译器 API 把一小段虚拟模块编译到真实的ActionDef上,再读回诊断。区分度是内建的,不是靠正文声称的:同一批坏赋值会被第二次编译到
IndexSignatureOnly(一个只有[key: string]: any的接口,即第 2 步之前的ActionDef),并要求全部干净通过。这个对照组就是「revert 即红」的证明:撤掉提升,索引签名重新把它们吞掉,断言即由绿转红。已实测:把
order?: SpecActionInput['order']改成order?: any,精确地红 2 条(order rejects a string where the spec says number与区分度断言)。按 AGENTS.md 测试纪律,编译在模块作用域完成(约 2.9s 落在 import 阶段,不受任何 test/hook 超时约束),而不是放进
beforeAll(那会落到更窄的 10shookTimeout下,是把慢 setup 变成负载相关 flake 的标准做法)。vitest run --project unit:330 files / 4619 passedvitest run --project dom:433 files / 4466 passedpnpm type-check(全仓):0 errorspnpm --filter @object-ui/core lint:0 errors;改动的三个文件中actionKeys.ts与新测试 0 warning,ActionEngine.ts的 warning 数因删 cast 而下降不在本 PR 范围
索引签名保留 —— 删除它是第 3 步。断言「索引签名仍然存在」的那条反向 pin 保持绿色,它是本 issue 自己的完工检查。
🤖 Generated with Claude Code
Generated by Claude Code