Uh oh!
There was an error while loading. Please reload this page.
fix(plugin-grid): harvest row-action predicate fields from the object's userActions only - #5426
Merged
Conversation
…'s userActions only `userActions` names two different blocks. On a view it is toolbar policy (`UserActionsConfigSchema`: sort/search/filter/refresh/rowHeight/addRecordForm/ editInline/buttons, which rejects `edit` by name); on an object it is the CRUD-predicate block (edit/delete/create with `visibleWhen`/`disabledWhen`) — the only shape `listViewPredicates` can read, since its loop skips every non-object value. The `$select` harvest read the key view-first, so a spec-legal toolbar block — written by `SpecBridge.transformListView` onto the very `object-grid` node the renderer receives, and by app-shell's `ObjectView` unconditionally — shadowed the object's CRUD predicates. The harvest found none, the predicate's operand left the projection, CEL faulted on the absent key and the row Edit/Delete button failed closed for everyone (objectui#3501), with a success receipt at every step. The harvest now reads the resolved object block only. Both read sites carry a comment naming the collision, and `gridNonAuthorKeys.test.tsx` pins each clause of it. Maintainer ruling 2026-08-20 on objectui#5240 (Q1=A, Q3=B). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RV6yuVCxymHYE16PL9vQkE
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
os-support-ai
marked this pull request as ready for review
August 20, 2026 17:06
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 20, 2026
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#5240
Implements the maintainer ruling of 2026-08-20 (「4 张 同意」): Q1=A · Q2=B · Q3=B.
What was wrong
userActionsnames two different blocks, and the grid's$selectharvest read them as one.userActionsis toolbar policy — the spec'sUserActionsConfigSchema:sort,search,filter,refresh,rowHeight,addRecordForm,editInline,buttons. It rejectseditby name.userActionsis the CRUD-predicate block —edit/delete/createcarryingvisibleWhen/disabledWhen(objectui#2614). It is the only shapelistViewPredicatescan read: its loop skips every non-object value.The harvest that decides which fields the server is asked for read the key view-first:
So a perfectly legal toolbar block shadowed the object's CRUD predicates. Every value in it is a boolean, the harvest collected zero predicates, the predicate's operand left the projection, CEL faulted on the absent key, failed closed, and the row Edit/Delete button disappeared for everyone with nothing pointing at the projection — objectui#3501's failure shape, reached with a success receipt at every step.
The view-level block is not hypothetical. Re-measured on this branch,
@objectstack/spec@17.0.0:UserActionsConfigSchema.shapekeyssort, search, filter, refresh, rowHeight, addRecordForm, editInline, buttonsUserActionsConfigSchema.safeParse({edit:{visibleWhen}})success: false,unrecognized_keys: ["edit"]ListViewSchema.safeParse(… userActions:{search:true,sort:true})success: trueListViewSchema.safeParse(… userActions:{edit:{visibleWhen}})success: false,unrecognized_keys: ["edit"]at pathuserActionsComponentPropsMap['object-grid'].safeParse({objectName, userActions})success: false,unrecognized_keys: ["userActions"]And two live producers write the toolbar block onto the view:
packages/react/src/spec-bridge/bridges/list-view.ts(insidebridgeListView, which emits atype: 'object-grid'node — i.e. straight into the read this PR fixes; pinned byP1SpecBridge.test.ts) andpackages/app-shell/src/views/ObjectView.tsx(built unconditionally as an object literal).What changed
packages/plugin-grid/src/ObjectGrid.tsx— the$selectharvest readsresolvedSchema.userActionsonly. The view-schema-first operand is gone.userActionsread sites carry a comment naming the collision — the surviving object-block read atresolveRowCrudAffordancestoo (Q3=B).packages/plugin-grid/src/__tests__/gridNonAuthorKeys.test.tsx— a new section pinning each clause of that comment. 11 new assertions.patchchangeset on@object-ui/plugin-grid.Why the wording is load-bearing
An earlier dispatch of this card stopped without a PR: the original ruling's premises — "zero producers", "an undeclared view-level key", "an ADR-0049 dead override" — were falsified by measurement, one of them in the very file the card cited as proof. The action survived; the stated reasons did not. Q1=A exists to keep the falsified claim out of the census artifact, so the comment and the pin state the measured reason instead.
Concretely, the pin does not assert "no producer writes this" and does not call the key non-author surface. It asserts:
edit/delete/createby name, and accepts the toolbar vocabulary (the control that makes those refusals mean something);SpecBridge.transformListViewreally copies it onto theobject-gridnode — the producer is the evidence, exactly as in theNON_AUTHOR_KEYStable above it, except here it proves the opposite;listViewPredicatesharvests zero fields from the toolbar block and the operand from the CRUD block;$select, with a toolbar block present on the view.The render channel of the surviving object-block read is already pinned four times over by
rowCrudEffectiveOps.test.tsx'suserActionsopt-out control group, so it is cited in the docblock rather than duplicated.Verification — all at
efc95b643No build artifact sits between any edit and any measurement here:
vitest.config.mtsaliases every@object-ui/*specifier to that package'ssrc/, and this worktree had nodist/at all while the suites ran (packages/core/dist,packages/react/dist,packages/plugin-grid/distall absent), so source is provably what executed.@objectstack/specis the published 17.0.0 fromnode_modules; nothing local builds it. The dependency closure was built afterwards, only becausetsc --noEmitneeds the.d.tsfiles.Run from the repository root, per AGENTS.md:
Reverse-verification (two legs, predicted before running, fix committed first)
Leg 1 — restore the view-first
??. Predicted: exactly one red, "…and a spec-legal toolbar block on the view does not knock it out"; the sibling case stays green because its fixture carries no view-level key. Observed:1 failed | 30 passed, that assertion,expected [ 'id', 'name' ] to include 'status'. Matches.Leg 2 — delete the
userActionsline from the harvest entirely (the "tidy finish" mutation). Predicted: two red — both projection cases losestatus. Observed:2 failed | 29 passed. Matches.Leg 2 is why the first projection case is counted at all: it is green on both legs of leg 1, so on its own it pins nothing about this fix — it pins that the read still exists, and leg 2 is what shows it can go red.
Scope
plugin-gridonly, per Q2=B. The identical (and worse — its left operand is always truthy on the app-shell path) instance atpackages/plugin-list/src/ListView.tsxis #5398, deliberately serial behind this PR so both read sites end up with one shape; out of scope here, and #5398 remains open. The producer-side question — the bridge writing a keyComponentPropsMap['object-grid']refuses by name — is routed through triage as its own spec-coordination card and is not addressed here either. Independently re-confirmed while checking downstream impact:ListViewreadsschema.userActionsas toolbar policy and does not forward it into its child grid node, so the app-shell path is unaffected by this change.Toolbar policy itself is untouched — it was never read through this path.
Generated by Claude Code