feat(grid): built-in row Edit/Delete honor per-record CEL predicates (#2614)#2617
Merged
Conversation
…2614) The built-in row CRUD actions were gated only by object-level booleans (resolveCrudAffordances), so an author could not hide/disable the Edit button for a subset of rows (e.g. frozen snapshot records) — the button that can edit couldn't be gated per-record, and the custom action that can be gated can't open the native editor. userActions.edit / userActions.delete now also accept the object form { enabled?, visibleWhen?, disabledWhen? } (spec: RowCrudActionOverrideSchema). The predicates are evaluated per row on the canonical CEL engine via useRowPredicate — the exact machinery custom row actions already use — with the record bound as record.* and bare fields. visibleWhen false → item not rendered (fail-closed); disabledWhen true → rendered disabled (fail-soft; the server hook remains the enforcement). Wiring: - plugin-grid: resolveRowCrudAffordances passes editPredicates / deletePredicates through; RowActionMenu renders built-in Edit/Delete via BuiltinRowActionItem (hook-safe, evaluated only when the row's kebab menu opens — Radix mounts content lazily, so declared predicates cost nothing at grid render time). - components: data-table's built-in Edit/Delete honor the new rowEditPredicates / rowDeletePredicates schema props via DataTableBuiltinRowActionItem (same posture); this is the path a detail page's related list renders through — the master-detail case from the downstream report. - plugin-detail: RelatedList sources the predicates from the child object's userActions; record-details resolves the object form's enabled for the inline-edit gate. - plugin-form: the managed-object blanket lock accepts the object form by its explicit enabled flag only. - app-shell: crudAffordances mirror kept in lockstep with the framework helper. Omitting the predicates (or using plain booleans) keeps today's behavior bit-for-bit — zero regression, zero added evaluation work. Closes #2614 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnCZhYpQjRyg2E44RHBiNE
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnCZhYpQjRyg2E44RHBiNE
Contributor
❌ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
os-zhuang
marked this pull request as ready for review
July 16, 2026 16:33
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.
Summary
Closes #2614 — the built-in row CRUD actions (Edit/Delete) only honored object-level booleans from
resolveCrudAffordances, so the button that can edit couldn't be gated per record, and the custom action that can be gated can't open the native editor.userActions.edit/userActions.deletenow also accept the object form:Predicates evaluate per row on the canonical CEL engine via
useRowPredicate— the exact machinery custom row actions already use — with the record bound asrecord.*and bare fields, ambientfeatures/userscope merged.visibleWhenfalse → item not rendered (fail-closed, warn once);disabledWhentrue → rendered disabled (fail-soft — the server hook remains the real enforcement).Wiring
resolveRowCrudAffordancespasseseditPredicates/deletePredicatesthrough (object-level opt-out semantics unchanged;enabled:falsebehaves likefalse);RowActionMenurenders built-in Edit/Delete via a new hook-safeBuiltinRowActionItem.rowEditPredicates/rowDeletePredicatesschema props viaDataTableBuiltinRowActionItem(same posture). This is the path a detail page's related list renders through — the master-detail case in the downstream report.RelatedListsources the predicates from the child object'suserActions;record-detailsresolves the object form'senabledfor the inline-edit gate.enabledflag only.crudAffordancesmirror kept in lockstep with the framework helper (surfaceseditPredicates/deletePredicates).Spec-side change (
RowCrudActionOverrideSchema,resolveCrudAffordancespredicates pass-through) is the companion PR objectstack-ai/framework#3076.Performance
useRowPredicateshort-circuits onnull/boolean before touching the CEL engine: zero added work for every existing app.DropdownMenuContentlazily), i.e. two small CEL evaluations per menu-open, not per rendered row.Per the issue's note on
master_detailparents: this lands feature (1) — authors denormalize the frozen/status flag onto the child and gate onrecord.*. Parent-record injection into the evaluation context is deferred as the optional follow-up.Tests
rowCrudAffordances.test.ts— predicate pass-through,enabled:falseopt-out, boolean-equivalence, not-wired case (10 tests).RowActionMenu.test.tsx— enabled/disabled/hidden per row, envelope form, click suppression when disabled, fail-closed vs fail-soft fault posture (13 tests).data-table-builtin-row-action-predicates.test.tsx— same matrix on the related-list path.crudAffordances.test.ts(app-shell mirror) — bucket defaults, boolean overrides, object form.🤖 Generated with Claude Code
https://claude.ai/code/session_01HnCZhYpQjRyg2E44RHBiNE
Generated by Claude Code