feat(lint): flag an action nobody placed — ADR-0078 Phase 3 action-locations - #4501
Merged
Merged
Conversation
New advisory rule `action-no-placement`: an action that declares no `locations` and that no list view places by name renders on NO surface — it parses, publishes, shows up in Setup, and no user can ever click it. ADR-0078 names this shape in its opening paragraph and Phase 3 asks for exactly this rule; the shared completeness predicate it envisioned was never built, so this lands standalone, one verified shape at a time. What made it verifiable now: objectui#3142 collapsed four disagreeing renderers onto one placement predicate. Before that, action:bar and the record header rendered an UNdeclared action anyway, so the shape only looked inert on paper. As of objectui 17.1 it is measurably inert. Two deliberate non-findings: - `locations: []` — the documented headless action (callable over REST / MCP / AI, no UI surface). ADR-0110 D3 refuses an undeclared handler, so this is the only legal way to expose one; flagging it would fight that ADR. The rule distinguishes "nowhere, deliberately" (`[]`) from an unstated placement (key absent) and reports only the latter. - Actions a view places by NAME — bulkActions, bulkActionDefs (including `execution: 'aggregate'` defs, whose whole point is an action with no single-record home) and rowActions, across all three list-view tiers: views[i].list, views[i].listViews.<key>, and the object-embedded objects[i].listViews.<key>. Advisory, never fatal: a view in another installed package may be the one placing the action — the same reason validateSemanticRoles and lintLivenessProperties warn rather than gate. Verified zero findings against app-showcase, app-crm and app-todo. Also in this change: - metadata-protocol's action form schema stops declaring `shortcut` / `bulkEnabled`. Both are spec-17 retiredKey() tombstones, and this schema is what the Studio designer renders its fallback form from — advertising them handed authors two inputs that could only ever produce an unsaveable draft (objectui#3145 removed the matching dedicated controls). - content/docs/ui/actions.mdx said naming an action in a widget never bypasses location filtering. The selection bar IS the exception, which the showcase's own task.view.ts comment already stated — the two now agree. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S9aiswZBzoVYsyLKRuGByE
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 3 package(s): 24 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
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.
objectui 侧配套:objectstack-ai/objectui#3154(移除同两个退役 key 的授权控件)。
新规则
action-no-placement(advisory)一个动作既没声明
locations、也没有任何列表视图按名字放置它,就渲染不到任何表面 —— 它能解析、能发布、在 Setup 里能看到,而没有任何用户点得到它。ADR-0078 开篇就点名了这个形态(「a
summarywith nosummaryOperations; anactionwith nolocations; … Each parses, "renders", reports success — and does nothing」),Phase 3 要的正是这条规则。ADR 设想的共享完整性谓词从未被建造(其 Status 行自陈),所以本次按 Phase 3 自己的说法「one verified shape at a time」独立落地。为什么现在才可验证:objectui#3142 之前,
action:bar和记录头部会把未声明的动作照样渲染出来 —— 这个形态在纸面上看着 inert,实际不是。四个渲染器统一到一条放置规则之后(objectui 17.1),它才真正可测量地 inert。两处刻意不报
locations: []—— 文档化的 headless 动作(actions.mdx的「Headless actions: declare it, then hide it」:保留能力门、参数契约、审计轨迹,只是不占 UI 表面)。ADR-0110 D3 又拒绝未声明的 handler,所以 headless 声明是暴露这类动作的唯一合法写法,报它等于和那条 ADR 打架。规则因此区分「明确说了哪都不放([])」与「根本没说(key 缺失)」,只报后者。bulkActions、bulkActionDefs(含execution: 'aggregate'的定义,这类动作按构造就没有单记录位置)、rowActions,覆盖三层列表视图:views[i].list、views[i].listViews.<key>,以及对象内嵌的objects[i].listViews.<key>(对象没有顶层list,漏掉这层会误报对象自己视图放置的动作)。严重度
warning/tier: 'advisory',与validateSemanticRoles、lintLivenessProperties同类 —— 另一个已安装包里的视图可能才是放置方。ADR-0078 §1 也规定「error只用于完全 inert,degrade 的用warning」,而这里存在真实的跨包漏报面。一并修掉的两处
metadata-protocol的动作表单 schema 不再声明shortcut/bulkEnabled。两者都是 spec 17 的retiredKey()墓碑,而这份 schema 正是 Studio 设计器渲染回退表单的依据 —— 继续声明等于给作者两个只能产出存不下去的草稿的输入框(objectui#3154 删的是与之配对的专用控件)。content/docs/ui/actions.mdx的一句话过宽:它断言"在 widget 里命名动作不绕过位置过滤",而 showcase 自己的task.view.ts写着 bulk 场景"命名它就是全部声明"。两者其实都对 —— 选中条是唯一的例外(这正是action.bulkEnabled墓碑的原话所规定的)。现在文档说准了,不再与自己的示例互相矛盾。测试与验证
[]与缺失的区分、对象内嵌动作、无名动作跳过,以及模板要求的 floor(clean /{}/null)。authoring-rule-wiring.test.ts会读规则源码校验 advisory 声明是否自洽、source路径是否存在、是否被命令文件直接 import)。packages/lint744 项、packages/cli689 项全通过;全量构建通过。app-showcase/app-crm/app-todo跑objectstack lint,该规则均无输出。🤖 Generated with Claude Code
https://claude.ai/code/session_01S9aiswZBzoVYsyLKRuGByE
Generated by Claude Code