feat(spec): userActions.edit/delete accept per-record CEL predicates (objectui#2614)#3076
Merged
Merged
Conversation
…(objectui#2614)
Extend the object-level userActions.edit / userActions.delete flags from
plain booleans to a union with an object form
{ enabled?, visibleWhen?, disabledWhen? } (RowCrudActionOverrideSchema),
so the built-in row Edit/Delete affordances can be hidden or disabled per
record via CEL predicates — the same evaluation contract custom actions
already use. Semantics: visibleWhen false → not rendered (fail-closed);
disabledWhen true → rendered disabled (fail-soft). The predicates are
advisory UI gating; server enforcement stays with permissions/hooks.
resolveCrudAffordances() keeps returning the resolved booleans (enabled
falls back to the managedBy bucket default) and now surfaces the
predicates as editPredicates / deletePredicates, pass-through as
authored. Boolean-only inputs produce byte-identical output — zero
behavior change for existing schemas.
clampManagedObjectWrites (ADR-0092 D2 hint clamp) treats the object form
by its explicit enabled flag only: per-record predicates are not a write
grant, so a managed object stays fail-closed unless enabled === true.
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.
|
Contributor
📓 Docs Drift CheckThis PR changes 3 package(s): 100 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnCZhYpQjRyg2E44RHBiNE
…e exports Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnCZhYpQjRyg2E44RHBiNE
…58 expression ledger The new data/object.zod.ts:visibleWhen / :disabledWhen surfaces (objectui#2614) tripped the conformance ratchet. Two rows, matching their actual fail policies: visibleWhen is fail-closed (a faulting predicate hides the row button), disabledWhen is fail-soft-log (a faulting predicate leaves it enabled; server hooks are the boundary). Both interpret on the canonical celEngine via objectui useRowPredicate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnCZhYpQjRyg2E44RHBiNE
… (objectui#2614) A PAID invoice's fields already freeze via readonlyWhen; the built-in row actions now follow the same truth: Edit renders DISABLED on paid rows (disabledWhen), Delete is HIDDEN outright (visibleWhen). Draft and sent invoices keep the untouched menu. Browser-verified against the objectui HMR console: paid row menu = greyed Edit only; sent row menu = enabled Edit + Delete. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HnCZhYpQjRyg2E44RHBiNE
os-zhuang
marked this pull request as ready for review
July 16, 2026 16:44
This was referenced Jul 17, 2026
os-zhuang
added a commit
that referenced
this pull request
Jul 17, 2026
content/docs/references/** is generated from packages/spec and committed, but no CI job ever regenerated and diffed it, so the public reference docs drifted silently while main stayed green. #3076 added RowCrudActionOverride to the spec and the docs never learned the type existed. Regenerate: 7 files, every change traced to a spec change that shipped without re-running the generator — RowCrudActionOverride and ServiceSelfInfo missing outright, dashboard filterBindings/name missing, readonly (#2948/#3003) and allowTransfer (#3004) stale, and connector ADR-0096 → ADR-0097 (both ADRs exist and are distinct, so the published docs were pointing readers at the wrong one). Verified deterministic: two consecutive runs produce byte-identical output. Gate: build-docs.ts --check, following the sibling convention. Every write goes through emit() and every wiped folder through manageDir(), so check and write run identical generation logic and differ only in the final disposition — it cannot pass on output a real run would not produce. Verified output-identical to the previous generator across all 258 files, and proven to fail on stale content, a missing page, a stale leftover page, and a vacuous no-schema run. Not `git diff --exit-code`: that misses untracked files. Placement: lint.yml's "TypeScript Type Check" — no paths filter and a required status check, so the gate can neither go dormant nor be merged past. ci.yml's "Build Docs" is gated on a `docs` filter excluding packages/spec/**, so it skips exactly the spec-only PRs that cause this drift. Also un-dormants the sibling gates: check:spec-changes / check:upgrade-guide read the ADR-0087 registries but ran under a filter listing only skills/**, and that filter watched content/docs/guides/skills.mdx, a path #2584 moved. Job renamed check-skill-docs → check-generated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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
Spec slice of objectstack-ai/objectui#2614 — the built-in row CRUD actions (Edit/Delete) could only be gated by object-level booleans, so an author could not hide/disable the Edit button per record (e.g. frozen snapshot rows).
userActions.edit/userActions.deletenow accept, in addition to the plain boolean, an object form (RowCrudActionOverrideSchema):ExpressionInputSchema(CEL; string shorthand normalizes to the{ dialect: 'cel', source }envelope). Unknown keys are rejected (.strict()), so e.g. ahideWhentypo fails at parse time.visibleWhenfalse → button not rendered (fail-closed);disabledWhentrue → rendered disabled (fail-soft). Advisory UI gating only — server enforcement stays with permissions/hooks.resolveCrudAffordances()keeps returning resolved booleans (enabledfalls back to themanagedBybucket default) and now surfaces the predicates aseditPredicates/deletePredicates, passed through as authored. Boolean-only inputs produce byte-identical output — zero behavior change for existing schemas.clampManagedObjectWrites(ADR-0092 D2/me/permissionshint clamp) treats the object form by its explicitenabledflag only: per-record predicates are not a write grant, so managed objects stay fail-closed unlessenabled === true.The UI consumption (grid row menu + related-list data-table) lands in the companion objectui PR on the same branch name.
Tests
packages/spec/src/data/object.test.ts— boolean back-compat, string-shorthand normalization, predicate pass-through with bucket-defaultenabled, boolean-equivalent object form, unknown-key rejection.packages/plugins/plugin-hono-server/src/fold-wildcard-superuser.test.ts— object form counts as opt-in only via explicitenabled: true.@objectstack/spec+@objectstack/plugin-hono-serverbuild clean (dts included).🤖 Generated with Claude Code
https://claude.ai/code/session_01HnCZhYpQjRyg2E44RHBiNE
Generated by Claude Code