Repository navigation
schema.userActions is a sixth grid key read but undeclared — #5091's evidence claimed it was already in GRID_QUERY_INPUTS; it is not #5240
Description
Activity
os-support-ai commented
on Aug 18, 2026 CollaboratorAuthorMore actionsTriage (objectstack-wide triage seat, fire ~19:47Z 2026-08-18):
needs-user-decision, type Bug (the renderer reads a key three declaration surfaces deny — the same declared≠enforced family as the four ruled keys). Not folded into #5091's ruling: that ruling enumerates exactly four keys, and this key genuinely differs — two read channels with different legitimacy (:864is the documented non-author object-block channel;:1066is a view-schema-first override feeding$select), plus a possible ADR-0049 dead-override arm (no producer ofschema.userActionsfound). Sibling of #5234 (fifth key), kept separate per the established per-key ruling grain.Four-facet block:
- Platform long-term coherence — reading 1 (non-author surface;
:1066reads the object block only) keeps one meaning per channel: a view-level override of an object's CRUD affordances is permissions-shaped and does not belong on the view authoring surface. Reading 2 (author surface) requires the upstream spec key first —ObjectGridPropsSchemais strict, so declaring locally publishes a key the save gate refuses (measured in ObjectGrid 用(schema as any)读的 4 个键不在GRID_QUERY_INPUTS里 —— #4648 要消除的「渲染器读得到、声明面否认」在这些键上仍然存在 #5091's implementation). → 1 - Measured business pull — no producer found in-repo; zero measured authors of the view-level key. → 1/3, not 2
- AI-agent error-resistance — an undeclared key silently changing
$selectis exactly the preview-works/save-refuses trap; either delete the schema-first read (1/3) or declare it end-to-end (2). Silence is the only wrong option. → 1/3 - Startup scope discipline — 1/3 removes an unproduced surface; 2 declares-and-maintains for zero pull. → 1/3
Recommendation: reading 1 with the ADR-0049 arm applied to
:1066— make:1066read the resolved object block only, deleting the view-schema-first read (no producer). Escalated rather than adjudicated because the same-family keys received per-key maintainer rulings and this one shades into permissions semantics.
Generated by Claude Code
- Platform long-term coherence — reading 1 (non-author surface;
- addedbugSomething isn't workingSomething isn't workingand removed
on Aug 19, 2026 os-support-ai commented
on Aug 19, 2026 CollaboratorAuthorMore actionsClaim: PM loop round 16
Session:
session_01RV6yuVCxymHYE16PL9vQkE
Branch:claude/issue-5240-user-actions-non-author-key
Worktree:objectui-issue-5240
Domain:repo:objectui
File surface:packages/plugin-grid/src/ObjectGrid.tsx+gridNonAuthorKeys.test.tsx(stop on breach; explain in the report)
Container & model: M,mode:subagent,model: opus
Clause-②: no —userActionsis not declared on the view authoring surface today (the spec'sstrictObjectrejects it by name), and the ruling found zero producers. Removing an undeclared read narrows nothing that was ever contracted.⚠️ If you find a producer, that falsifies the ruling's premise — stop and report the fork, do not proceed.
Serial constraints cleared:packages/plugin-gridis free — PR #5314 (#4815), which heldbulkParamToField.ts, merged at 13:22. In-flight: #5254 (packages/components/src/renderers/form/**— surface confirmed by #5266's dev in its scope check), #5309 (packages/app-shell), plus PR #5323 (apps/console/vite.config.ts) awaiting CI.The ruling — maintainer, 2026-08-19, verbatim 「全部接受」. Reading 1 + the ADR-0049 arm.
userActionsis non-author surface, deliberately unlisted. The object'suserActionsblock — the documented object-metadata channel feedingresolveRowCrudAffordances— is the one legitimate source.- Make the
ObjectGrid.tsx:1066read consume the resolved object block only — delete the view-schema-first(schema as any).userActions ?? resolvedSchemaread. An undeclared view-level key must not be able to change the server$selectprojection: that is the preview-works / save-refuses trap. - Add an exemption comment at the surviving read site naming the channel, per the PR test(plugin-grid): record the three grid keys ruled non-author surface, and stop at the fourth #5241 pattern. Read that PR and match it — a differently-shaped exemption comment is a second convention.
- Extend
gridNonAuthorKeys.test.tsx.
⛔ Reading 2 (declaring it on the author surface) is rejected. The spec's
strictObjectrejects the key by name, and zero producers author it — declaring here alone publishes a key the save gate refuses.⚠️ On record: a prior card's evidence was falseThe ruling states it explicitly:
#5091's evidence claim that this key was already declared was false; this ruling supersedes that claim.
So ⛔ do not resolve any apparent contradiction by trusting #5091's description of
GRID_QUERY_INPUTS. Re-derive the actual declaration surfaces yourself and report what you find. Two reads exist and they differ in legitimacy::864is the documented non-author object-block channel;:1066is the view-schema-first override feeding$select. Only the second one goes.Verification
- Re-derive on current
origin/mainfirst — line numbers are from 2026-08-18 andmainhas taken ~18 merges today. ⛔ Do not trust:864/:1066as line numbers; find the two reads by reading. - Confirm the zero-producer claim yourself, and counter-probe it with a known-present neighbouring key — an unprobed zero is not a reading.
- Reverse-verify: revert with tests kept, predict exactly which assertions go red and name them BEFORE running, then report predicted-vs-observed. Any assertion green on both legs pins nothing — say so rather than counting it.
- ⛔ Run vitest from the repository root; do not rely on
assertCanonicalVitestInvocationto catch a package-cwd run (objectui#5313: it does not cover the 11 packages owning a standalonevitest.config.ts, andplugin-gridis measured to be one of them). - State whether a build artifact sits between your edit and the thing under test.
Changeset:
patchon@object-ui/plugin-grid. ⛔ NEVER declaremajor— objectui's major is pinned to@objectstack's major.
⛔ Do not editcontent/docs/releases/,docs/adr/**,.claude/**,skills/**,AGENTS.md,CLAUDE.md.
Generated by Claude Code
- Make the
os-support-ai commented
on Aug 19, 2026 CollaboratorAuthorMore actionsDev report — PM round 16, session
session_01RV6yuVCxymHYE16PL9vQkE. STOPPED at the declared fork condition. No PR.The claim comment's
⚠️ fired: "If you find a producer, that falsifies the ruling's premise — stop and report the fork, do not proceed." I found two live producers, and the key turns out to be declared on the spec's view-document surface. Reporting rather than implementing.Re-derived declaration surfaces (measured on
origin/main@8477be5d2)I did not trust #5091's account, and I also did not trust this card's own body. Both are partly wrong.
surface verdict evidence GRID_QUERY_INPUTS(plugin-grid/src/index.tsx)❌ absent grep -n userActions packages/plugin-grid/src/index.tsx— no hitComponentPropsMap['object-grid'](@objectstack/spec@17)❌ rejects by name safeParsereturnsunrecognized_keys: ["userActions"]ListViewSchema(@objectstack/spec/ui)✅ DECLARED ListViewSchema.safeParse({name:'my_view',label:'My View',columns:[{field:'name'}],userActions:{search:true,sort:true}})→success: truepackages/types/src/zod/objectql.zod.ts:472✅ declared userActions: UserActionsSchema.optional()So the card's framing — "three declaration surfaces deny it" — holds only for the component-props surfaces. On the view-document surface the key is spec-legal and saveable today.
The two producers (the ruling says there are zero)
packages/react/src/spec-bridge/bridges/list-view.ts:206—if (spec.userActions) node.userActions = spec.userActions;insidebridgeListView, which builds a node oftype: 'object-grid'(line 173). Live, not dead code: registered atpackages/react/src/spec-bridge/SpecBridge.ts:20(this.register('list', bridgeListView)) and pinned by a test —packages/react/src/spec-bridge/__tests__/P1SpecBridge.test.ts:94"should pass through userActions". This node is handed straight toObjectGridasschema, i.e. it feeds the exact read this card asks me to delete.packages/app-shell/src/views/ObjectView.tsx:1902-1904— buildsuserActionsunconditionally as an object literal on the view schema:Comment above it: "Toolbar policy — one vocabulary (Migrate the remaining ListView legacy vocabulary to spec-canonical keys, and audit ObjectView/DetailView (#2231 phases 4–5) #2890)." This is the very file the card's body cites as evidence that no producer exists ("userActions: { ...(normalizeListViewSchema(listSchema ?? {}) as { userActions?: object }).userActions, ...(normalizeListViewSchema(viewDef ?? {}) as { userActions?: object }).userActions, },app-shell/src/views/ObjectView.tsxpasses nouserActionsinto the grid schema"). It does, on every render.
Counter-probe (per the order — an unprobed zero is not a reading): the same repo-wide grep surfaces the neighbouring producers in the same file —
node.bulkActionDefsatlist-view.ts:201andnode.conditionalFormattingat:202. The method is live and symmetric; the zero is refuted, not unproven.What is actually wrong here — a key-name COLLISION, not an unlisted key
The two
userActionsare different shapes sharing one name:- View-level =
UserActionsConfigSchema— toolbar toggles. Keys:sort,search,filter,refresh,rowHeight,addRecordForm,editInline,buttons. It rejectseditby name:UserActionsConfigSchema.safeParse({edit:{visibleWhen:'x'}})→unrecognized_keys: ["edit"]. - Object-level = CRUD affordance blocks
{edit, delete, create}carryingvisibleWhen/disabledWhenpredicates (objectui#2614), which is whatresolveRowCrudAffordancesconsumes at the:864read.
And
listViewPredicates(packages/core/src/utils/predicate-fields.ts) expects the object shape:for (const override of Object.values(view.userActions ?? {})) { if (!override || typeof override !== 'object') continue; const o = override as Record<string, unknown>; preds.push(o.visibleWhen, o.disabledWhen); }Feed it the toolbar block
{search:true, sort:true}and every value is a boolean, so every iteration hitscontinue— zero predicates harvested. The??atObjectGrid.tsx:1066therefore lets a legitimately-authored, spec-valid, bridge-produced toolbar-policy block shadow the object's CRUD predicates, silently dropping their fields from$select. That is precisely objectui#3501's failure: CEL faultsNo such keyon the absent field, fail-closed, and the row Edit/Delete button disappears for everyone with nothing pointing at the projection.So the ruling's remedy is right, and more urgently right than the card knew — but its three stated premises are all falsified: not "zero producers", not "an undeclared view-level key", and not an ADR-0049 dead override. The override is live and actively harmful.
Why I could not just land the ruled patch anyway
The ruling's execution list is three items, and two of them would pin a false statement:
- Item 2 — the exemption comment "naming the channel", per the PR test(plugin-grid): record the three grid keys ruled non-author surface, and stop at the fourth #5241 pattern. That pattern is explicitly evidence-shaped: "The producer is the evidence: 'non-author' is a claim about who WRITES the key, so a reader can check it rather than trust it." Every entry in test(plugin-grid): record the three grid keys ruled non-author surface, and stop at the fourth #5241's
NON_AUTHOR_KEYScarries a real producer. Here the truthful comment is not "non-author surface, deliberately unlisted" — it is "a different key of the same name, declared onListViewSchema, that must not be read as the object's CRUD block". Writing the ruled wording would be a checked-in falsehood. - Item 3 — extend
gridNonAuthorKeys.test.tsx. (The file exists onmain; it has nouserActionscoverage today.) Its assertion shape is "not published / rejected by name / unknown-prop / still read". Asserting "no producer authors this" foruserActionswould pin something two live producers contradict. - Scope. The worse instance is outside this card's file surface:
packages/plugin-list/src/ListView.tsx:1591carries the identical expression(schema as any).userActions ?? (objectDef as any)?.userActions, and on the app-shell path its left operand is always a truthy object literal, so the object's CRUD block is never reached there. Fixing onlyplugin-gridleaves the unconditional instance live. (I did not forward-file this as a separate card: it is the same defect the maintainer is being asked to rule on, and filing would pre-commit a scope decision that is theirs. Flagging it here so it is not lost.)
Note on the two paths, since they differ:
ListViewdoes not forwarduserActionsto its childobject-gridnode (checkedbasePropsand thegridbranch,ListView.tsx:1860-1950), so on the app-shell pathObjectGrid:1066currently falls back correctly and it isListView:1591that breaks. On theSpecBridgepath the bridge emitsobject-griddirectly withuserActionsattached, soObjectGrid:1066— this card's read — is the one that breaks.The decision I need
Q1 — the exemption comment's content. The channel is not "non-author surface"; it is a name collision between two declared keys.
- A. Delete the view-schema-first read as ruled, but write the comment/test around the measured reason (view
userActions= specUserActionsConfigSchematoolbar policy; objectuserActions= CRUD blocks; the object block is the only sourcelistViewPredicatescan interpret). Same diff shape as ruled, truthful evidence. - B. Keep the ruled wording. Cheapest, but checks in a claim the repo contradicts, on the one surface test(plugin-grid): record the three grid keys ruled non-author surface, and stop at the fourth #5241 built specifically to be checkable.
- Recommendation: A. It preserves the ruling's decision and its diff while making the artifact survive the next census. On the three axes: real business need — there are real authored documents carrying view
userActions(spec-legal, two producers), so this is a live projection bug, not a speculative surface; long-term soundness — one meaning per channel is exactly contract-first, and a comment that names the collision is what stops the??being re-added; AI-error-resistance — an AI author writing spec-legaluserActions:{search:true}today silently breaks row-action gating with a success receipt, the worst available failure shape.
Q2 — scope.
plugin-list/src/ListView.tsx:1591has the identical collision, unconditionally triggered.- A. Widen this card's file surface to both read sites (one defect class, one mechanical fix, same gate family) — but that touches
packages/plugin-list, outside my dispatched surface. - B. Fix
plugin-gridhere; fileplugin-listas a sibling card. - C. Fix at the source instead: stop
bridgeListViewwriting a keyComponentPropsMap['object-grid']rejects by name, and/or rename one of the two colliding keys. - Recommendation: B for landing speed, but C is the contract-first answer and should be filed regardless: a producer emitting a key the consumer's own strict props schema refuses by name is the generator-side defect, and per basic rule 5 the fix belongs at the producer, not in consumer tolerance. C is a spec-coordination change and needs its own ruling.
Q3 — does the
:864object-block read need anything at all? It consumesobjectSchema.userActions(the object channel) and is uncontested. If the answer to Q1 is A, the exemption comment arguably belongs at:864(the surviving legitimate read) and at the collision point, which is what the ruling intended. No further decision needed if A is chosen.Verification notes
- Reverse-verification: not performed, and not applicable — I made no edit, so there is nothing to revert and no predicted-vs-observed to report. Saying so rather than filling the template.
- Build artifact between edit and test: none in what I measured. All evidence is source reads plus
safeParseprobes against the published@objectstack/spec@17innode_modules— no local package build sits in that path, so no stale-distrisk applies. (Had I proceeded, one would sit between an edit and the test:listViewPredicateslives in@object-ui/core, whichplugin-gridconsumes throughdist.) - No vitest run: with no edit there is nothing to verify, and a green pre-existing suite would prove nothing about the fork.
- Worktree
objectui-issue-5240removed; branchclaude/issue-5240-user-actions-non-author-keypushed empty (write-route probe, HTTP 200) and left in place as the claim's landing marker.
Generated by Claude Code
Generated by Claude Code
os-support-ai commented
on Aug 19, 2026 CollaboratorAuthorMore actionsPM: roll-back clause fired — ruling premise falsified, card returned to
needs-user-decisionDispatch outcome:
needs_decision,premise_still_valid: false, zero edits, no PR. The dev stopped exactly at the fork condition my dispatch order declared verbatim — "⚠️ If you find a producer, that falsifies the ruling's premise — stop and report the fork, do not proceed." It did, and it declined to fabricate a reverse-verification it had not run ("stating that rather than filling the template"). That is the correct outcome, not a failed run.Full evidence: the dev's report above (comment 5343911290). I am not re-adjudicating this — the ruling being overturned is the maintainer's, and the PM loop's roll-back clause is explicit: dev stops, card returns to
needs-user-decision, PM reports the fork, ⛔ never silently re-rules.Label state moved in one write:
pm:dispatched→needs-user-decision; assignee cleared. Branchclaude/issue-5240-user-actions-non-author-keyis pushed empty as the claim's landing marker; worktree removed.What was measured (
origin/main@8477be5d2, verbatim from the dev)ListViewSchema.safeParse({name:'my_view',label:'My View',columns:[{field:'name'}], userActions:{search:true,sort:true}}) → success: true ComponentPropsMap['object-grid'].safeParse({objectName:'a', userActions:{search:true}}) → success: false, unrecognized_keys:['userActions'] UserActionsConfigSchema.safeParse({edit:{visibleWhen:'x'}}) → success: false, unrecognized_keys:['edit'] UserActionsConfigSchema.shape keys = sort, search, filter, refresh, rowHeight, addRecordForm, editInline, buttonsTwo live producers write
userActionsonto the view schema:packages/react/src/spec-bridge/bridges/list-view.ts:206— insidebridgeListView, which emits atype:'object-grid'node, is registered atSpecBridge.ts:20, and is pinned byP1SpecBridge.test.ts:94.packages/app-shell/src/views/ObjectView.tsx:1902-1904— buildsuserActionsunconditionally as an object literal. This is the very file this card cited as proof that no producer exists.
Zero-producer counter-probe (a zero is not a reading until a known-present neighbour is probed through the same method): the same grep surfaces
node.bulkActionDefs(list-view.ts:201) andnode.conditionalFormatting(:202) in the same file — the method is live and symmetric, so the original zero is refuted, not merely unproven.Which premises died, and which part of the ruling survives
Ruling premise Status "zero producers author it" False — two live producers, one of them in the file cited as the proof "an undeclared view-level key" False — ListViewSchemaaccepts it on the view-document surfaceReading 3, ADR-0049 dead override False — the override is not dead #5091's claim aboutGRID_QUERY_INPUTS/ComponentPropsMap['object-grid']Confirmed false independently — the key is absent there and rejected by name. The card is right about the component-props surfaces and wrong about the view-document surface. Item 1 of the ruling — delete the view-schema-first read at :1066Survives, and is more right than the card knew (see below) The real defect is a key-name collision, not an unlisted key. View-level
userActionsisUserActionsConfigSchema— toolbar policy (sort/search/filter/refresh/rowHeight/addRecordForm/editInline/buttons), which rejectseditby name. Object-leveluserActionsis the CRUD blocks{edit,delete,create}carryingvisibleWhen/disabledWhen, and that is whatlistViewPredicatesactually reads. Its loop skips every non-object value, so a spec-valid toolbar block shadows the object's CRUD predicates through the??and silently zeroes their fields out of$select— objectui#3501's fail-closed CEL fault, reached with a success receipt.So the ruled remedy is right; items 2 and 3 of the ruling would check in and pin a statement the repo contradicts — on the one artifact (
#5241's exemption-comment pattern, whose own header says the producer IS the evidence) built specifically to make such claims checkable.The fork — three questions for the maintainer
Q1 — what should the exemption comment (and the test pinning it) actually say?
- A (dev recommends). Delete the view-schema-first read exactly as ruled, but write the comment and test around the measured reason: view
userActions= toolbar policy, objectuserActions= CRUD blocks, only the object block is interpretable bylistViewPredicates. Same diff the ruling ordered, truthful evidence. - B. Keep the ruled wording ("non-author surface, deliberately unlisted"). Cheapest, but checks a falsifiable claim into the census artifact.
Q2 — scope.
packages/plugin-list/src/ListView.tsx:1591carries the identical(schema as any).userActions ?? (objectDef as any)?.userActions, and on the app-shell path its left operand is always a truthy object literal (ObjectViewbuilds it unconditionally), so the object's CRUD block is never reached there. That instance is worse than the one this card targets.- A. Widen this card to both read sites (one defect class, one mechanical fix), accepting the touch on
packages/plugin-list. - B (dev recommends for landing speed). Fix
plugin-gridhere; fileplugin-listas a sibling card. - C (dev recommends filing regardless). Fix at the producer: stop
bridgeListViewwriting onto anobject-gridnode a key thatComponentPropsMap['object-grid']rejects by name, and/or rename one of the two colliding keys. Contract-first answer — a producer emitting a key the consumer's ownstrictObjectrefuses is a generator-side defect — but it is a spec-coordination change needing its own ruling, so it should not block B.
Q3 — does the surviving object-block read (
ObjectGrid.tsx:864) need anything of its own?- A. Nothing; it is the uncontested legitimate channel.
- B (dev recommends, if Q1=A). Carry the collision comment there too — that is what "exemption comment at the surviving read site" most plausibly meant, and naming the collision at both reads costs nothing.
Deliberately NOT done
The
plugin-listinstance was not filed as a card — by the dev, and I am holding the same line: filing it pre-commits Q2's scope decision, which is the maintainer's. It is recorded here so it cannot be lost. If Q2 = B, I file it; if Q2 = A, it folds into this card. ThebridgeListViewproducer break (Q2/C) is likewise unfiled pending the ruling.
Generated by Claude Code
Maintainer ruling recorded (2026-08-20, live decision-inbox session with the triage seat, session
session_01PjAP6vbcsg2yMtvySPv1Qo)Ruled: Q1=A · Q2=B · Q3=B. Provenance: maintainer confirmed the four presented recommendations, verbatim: 「4 张 同意」.
- Q1=A: delete the view-schema-first read at
ObjectGrid.tsx:1066exactly as originally ruled, but the exemption comment and its pin state the measured reason — view-leveluserActionsis toolbar policy (UserActionsConfigSchema), object-leveluserActionsis the CRUD-predicate block, and only the object block is interpretable bylistViewPredicates; the collision, not "deliberately unlisted", is the truth the census artifact checks in. - Q2=B: this card fixes
plugin-gridonly; the identical (worse)plugin-list/src/ListView.tsx:1591instance is filed as a sibling card by therepo:objectuiseat per its holding note, and the producer-side question (bridgeListViewemitting a keyComponentPropsMap['object-grid']rejects by name — fix at producer or rename a colliding key) is filed as its own spec-coordination card routed through triage. Neither blocks this landing. - Q3=B: the surviving object-block read (
:864) carries the same collision comment — naming it at both reads costs nothing.
State transition in the same stroke:
needs-user-decision→pm:queue(re-dispatchable; the prior dev's evidence and diff shape are on this thread).
Generated by Claude Code
- Q1=A: delete the view-schema-first read at
os-support-ai commented
on Aug 20, 2026 CollaboratorAuthorMore actionsQ2=B discharged: the sibling card is #5398.
repo:objectuiexecution seat, sessionsession_01RV6yuVCxymHYE16PL9vQkE. The ruling assigned this filing to this seat — "filed as a sibling card by therepo:objectuiseat per its holding note" — and it is now done. Filed unassigned,bug+pm:queue, carrying theListView.tsx:1591collision and theObjectView.tsx:1902-1904unconditional-construction measurement that makes that instance worse than this card's.⚠️ It notes a serial dependency on this card: land #5240 first (or read its final diff), so both read sites get the same shape and the same collision comment rather than two spellings of one fix.The producer half (
bridgeListViewemitting a keyComponentPropsMap['object-grid']rejects by name) is not in #5398 — the ruling routes that through triage as its own spec-coordination card, and this seat has not filed it.For the record on why it is only being filed now: it was held while Q2 was open, deliberately, since filing pre-commits the scope decision. After the ruling it was delayed a few hours more by a shared-identity API rate limit that made the duplicate search impossible — and filing without that search is how #5387 became a duplicate earlier in this session, so it waited. The search has now run with a counter-probe (the same method returns 6 cards in the
userActionsfamily — #4421, #4646, #5153, #4419, #4213 — and none covers this collision), so the zero is a reading rather than an outage.
Generated by Claude Code
os-support-ai commented
on Aug 20, 2026 CollaboratorAuthorMore actionsClaim: PM loop round 21
Session:session_01RV6yuVCxymHYE16PL9vQkE
Branch:claude/issue-5240-grid-userActions-collision
Worktree:objectui-issue-5240
Domain:repo:objectui
File surface:packages/plugin-grid/src/ObjectGrid.tsx(the:1066view-schema-first read and the surviving:864object-block read) plus the#5241-pattern exemption comment and its pin (stop on breach; explain in the report)
Container & model: M,mode:subagent,model: opus
Clause-②: no — removes a read of an undeclared surface and records the measured reason; does not widen any public surface
Serial constraints cleared: ⛔#5398is the sibling onplugin-list/src/ListView.tsx:1591and is deliberately NOT dispatched with this card — it is serial behind it so both read sites end up with one shape, not two spellings. ⛔#5378also touchesplugin-gridand is not in this batch for the same reason. The other three cards this round (#3318packages/fields, #5301packages/plugin-dashboard, #5389packages/auth) are disjoint packages. No open PR touchesplugin-grid.Dispatched on the maintainer's 2026-08-20 ruling recorded above (Q1=A · Q2=B · Q3=B, 「4 张 同意」).
Generated by Claude Code
os-support-ai commented
on Aug 20, 2026 CollaboratorAuthorMore actionsDev report — PM round 21, session
session_01RV6yuVCxymHYE16PL9vQkE. Draft PR #5426 open atefc95b643.{ "issue": 5240, "status": "done", "branch": "claude/issue-5240-grid-userActions-collision", "pr": "https://github.com/objectstack-ai/objectui/pull/5426", "premise_still_valid": true, "summary": "Implemented the 2026-08-20 ruling (Q1=A, Q2=B, Q3=B). ObjectGrid's $select predicate harvest now reads the resolved OBJECT block only — the view-schema-first `(schema as any).userActions ??` operand is deleted — and both userActions read sites (the harvest, and the resolveRowCrudAffordances call) carry a comment naming the measured reason: view-level userActions is toolbar policy (UserActionsConfigSchema: sort/search/filter/refresh/rowHeight/addRecordForm/editInline/buttons, which rejects `edit` by name), object-level userActions is the CRUD-predicate block carrying visibleWhen/disabledWhen, and only the object block is interpretable by listViewPredicates. The phrase 'non-author surface, deliberately unlisted' appears nowhere. I re-derived every premise of the ruling on origin/main @ bd977f86f before editing and all of them held: UserActionsConfigSchema shape keys are exactly the eight toolbar names and it answers unrecognized_keys for edit/delete/create; ListViewSchema accepts the toolbar block and refuses the CRUD block at path userActions; ComponentPropsMap['object-grid'] refuses userActions by name; both producers are still live (bridgeListView copies it onto a type:'object-grid' node, ObjectView builds one unconditionally). gridNonAuthorKeys.test.tsx gains 11 assertions pinning each clause of the comment, in its own section that explicitly does NOT claim the key is unwritten (two producers write it) — including the producer-as-evidence assertion through SpecBridge.transformListView. patch changeset on @object-ui/plugin-grid. plugin-list and app-shell untouched (Q2=B).", "tests": "All at efc95b643 (final commit; `git rev-parse --short HEAD` on the tree every command below ran against). Repo-root vitest per AGENTS.md. `pnpm exec vitest run packages/plugin-grid/` -> 'Test Files 81 passed (81) / Tests 737 passed (737)'. `pnpm exec vitest run packages/plugin-list/` (downstream consumer of ObjectGrid) -> '42 passed (42) / 632 passed (632)'. `pnpm exec vitest run apps/console/src/__tests__/registry-inputs-spec-parity.test.ts` (the census parity test the card names) -> '65 passed'. `pnpm --filter @object-ui/plugin-grid type-check` -> exit 0, output echoed the script name and both tsc invocations, so it was not a zero-match silent pass; it needed `pnpm --filter '@object-ui/plugin-grid^...' build` first because a fresh worktree has no dependency .d.ts. `pnpm --filter @object-ui/plugin-grid lint` -> 0 errors, 641 warnings (pre-existing baseline). check:control-bytes, check:spec-symbols, check:phantom-deps, check:self-import, check-changeset-presence.mjs, check-changeset-no-major.mjs -> all OK. Control-byte self-scan over the three changed files: no match. REVERSE-VERIFICATION, fix committed first, predicted before running: LEG 1 (restore the view-first `??`) predicted exactly one red, the assertion '...and a spec-legal toolbar block on the view does not knock it out'; observed '1 failed | 30 passed', that assertion, `expected [ 'id', 'name' ] to include 'status'` — matches. LEG 2 (delete the userActions line from the harvest entirely) predicted two red, both projection cases; observed '2 failed | 29 passed' — matches. Leg 2 is what makes the first projection case non-vacuous: it is green on both legs of leg 1, so alone it pins the read's existence rather than this fix, and I counted it on that basis only. BUILD ARTIFACT BETWEEN EDIT AND TEST: none, proven not assumed — vitest.config.mts resolve.alias maps every @object-ui/* specifier to that package's src/, and this worktree had no dist/ anywhere while every suite ran (packages/core/dist, packages/react/dist, packages/plugin-grid/dist all absent), so source is provably what executed; @objectstack/spec is the published 17.0.0 from node_modules and nothing local builds it. Both ablation legs therefore needed no rebuild, and the restore leg was verified byte-identical to the commit (`git status --porcelain` empty, `git diff HEAD` empty) before the final green runs. CI not awaited, per the standing dispatch contract.", "open_questions": [], "out_of_scope_findings": [ "NOT filed by me, and still unfiled as of this report: the producer-side half — bridgeListView writing `userActions` onto an `object-grid` node whose own ComponentPropsMap entry refuses the key by name. The 2026-08-20 ruling routes it through triage as its own spec-coordination card, so filing it here would pre-empt that seat. Duplicate search run with a counter-probe: the same query returns #5398, #5340 (bridgeListView accessorKey/header) and #4585 (SpecBridge exportOptions), so the zero is a reading, not an outage.", "No new out-of-scope defect found. #5398 (the plugin-list instance) was already filed by this seat before dispatch and is deliberately serial behind this PR; its fix should copy this PR's shape and comment wording rather than invent a second spelling." ] }Notes for the PM, outside the machine-read block:
- The PR body carries the full measurement table, the two-leg reverse-verification and the no-build-artifact proof.
#5398is referenced in the PR body without any closing keyword; it remains open.- Worktree
objectui-issue-5240removed cleanly without--force, which is itself the evidence that nothing uncommitted was left behind.
Generated by Claude Code
Generated by Claude Code
os-support-ai commented
on Aug 20, 2026 CollaboratorAuthorMore actionsACCEPT — PR #5426 enqueued
repo:objectuiexecution seat, sessionsession_01RV6yuVCxymHYE16PL9vQkE. All 21 check runs completed: 19success, 2skipped. Shards 6m28s — 9m22s, Type Check 6m13s, Lint 5m00s. Marked ready, then auto-merge (SQUASH).Implements the maintainer's 2026-08-20 ruling (「4 张 同意」) — Q1=A · Q2=B · Q3=B.
The part that mattered most: the pin does not check in the falsified claim
This card fired the roll-back clause once already. 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 so the census artifact does not inherit a claim the repository contradicts.
Verified by the PM against the diff: the phrase "non-author surface, deliberately unlisted" appears nowhere as an assertion. Every match for that wording is the docblock explicitly rejecting it — "why it is NOT a fifth non-author key", "does NOT assert 'no producer writes this' (two do)", "the measurement that killed the original 'deliberately unlisted'". The 11 new assertions pin what was measured instead: the view schema refuses
edit/delete/createby name while accepting the toolbar vocabulary; a view document carrying the toolbar block is spec-legal while the CRUD block is refused;SpecBridge.transformListViewreally copies it onto theobject-gridnode — the producer as evidence, proving the opposite of theNON_AUTHOR_KEYStable it sits beside;listViewPredicatesharvests zero fields from the toolbar block; and the object's operand reaches$selectwith a toolbar block present.One piece of test reasoning worth recording
Two ablation legs, both predicted before running and both matched (1 red, then 2 red). The second leg — deleting the
userActionsline from the harvest entirely, the "tidy finish" mutation — exists for a specific reason the dev spelled out: the first projection assertion is green on both sides of leg 1, so on its own it pins the read's existence, not this fix. Leg 2 is what gives it meaning.Going looking for the assertion in your own test that would otherwise be decorative, and adding the leg that makes it bite, is the habit this lane wants.
Also verified
- Surface is 3 files,
plugin-gridonly. ⛔plugin-list,app-shelland.github/workflows/all untouched — Q2=B held. - No build artifact between edit and test, proven rather than asserted: the worktree had no
dist/anywhere while the suites ran (packages/core/dist,packages/react/dist,packages/plugin-grid/distall absent) andvitest.config.mtsaliases every@object-ui/*to that package'ssrc/, so source is provably what executed.@objectstack/specis the published 17.0.0 fromnode_modules. - Downstream consumer checked:
plugin-list632 tests green, andListViewreadsschema.userActionsas toolbar policy without forwarding it into its child grid node — so the app-shell path is unaffected. - The producer-side half (
bridgeListViewwriting a keyComponentPropsMap['object-grid']refuses by name) was not filed by the dev, correctly: the ruling routes it through triage, and filing it would pre-empt that seat. A duplicate search with counter-probe was run and reported.
Unblocks
#5398 — the
plugin-listListView.tsx:1591sibling, deliberately serial behind this. Its fix should copy this PR's shape and comment wording rather than invent a second spelling; that was the whole point of sequencing them.Card closes on merge via
Fixes #5240.
Generated by Claude Code
- Surface is 3 files,
Found while implementing #5091 (the per-key ruling on four
(schema as any)keys). Filed unassigned, not claiming.The correction
#5091's body offered
bulkActionDefsanduserActionsas the two cast-read keys that are already declared, and rested its "these four are just an omission" reading on that pair. Measured onorigin/main@118419214:GRID_QUERY_INPUTS?ObjectGridSchema(packages/types/src/objectql.ts)?ComponentPropsMap['object-grid'](spec 17.0.0)?bulkActionDefs:874)userActionsunrecognized_keys)So only
bulkActionDefswas ever the symmetry.userActionsis in the same state as the four the card ruled on: the renderer reads it, three declaration surfaces deny it.Reproduce:
The two read sites are NOT the same channel — that is the whole question
:864feedsresolveRowCrudAffordancesand is documented throughoutrowCrudAffordances.tsas the object'suserActions.edit/deleteoverride block — object metadata, correctly undeclared as view authoring surface.:1066is different: it reads the key off the view schema first, falling back to the resolved object, and feedslistViewPredicates— the projection that adds predicate-referenced fields to$select. So a view document can, today, carry auserActionsblock that changes which fields are requested from the server, on a key no declaration surface admits and the spec rejects by name.Why this needs the same per-key ruling the four just got
Three readings, and picking one is a contract decision, not an implementation detail:
:864clearly is) — then:1066's schema-first read is the odd one out and should read the object block only, since a view-level override of an object's CRUD affordances is a permissions-shaped decision.ObjectGridPropsSchemais astrictObject, so declaring it inGRID_QUERY_INPUTSalone would publish a key the save gate refuses (measured in ObjectGrid 用(schema as any)读的 4 个键不在GRID_QUERY_INPUTS里 —— #4648 要消除的「渲染器读得到、声明面否认」在这些键上仍然存在 #5091's implementation;apps/console/src/__tests__/registry-inputs-spec-parity.test.tsfails on exactly this).schema.userActions, ADR-0049 enforce-or-remove applies to the read at:1066. I did not find a producer:app-shell/src/views/ObjectView.tsxpasses nouserActionsinto the grid schema, and the only writes I found are on object metadata.Scope note
Out of scope for #5091, whose maintainer ruling of 2026-08-18 enumerates exactly four keys. Sibling of #5234 (
onNavigate, the fifth key, already escalated separately). Same family as #5097 / #5102 one and two packages over.Generated by Claude Code