You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
console: action:button / action:icon / action:menu bind the row only at the bare root, so a declared visible written as record.* faults and fails closed #4075
Out-of-scope finding from objectui#3055 (PR pending on claude/issue-3055-record-approval-actions). Filed per PD #10 — unassigned, ungraded. Concrete defect, not observation-class: a correctly-authored predicate hides the action it guards.
Fact
useCondition(pred, recordData, { throwOnError: true }) builds its evaluator as new ExpressionEvaluator({ ...scope, ...context }) (packages/react/src/hooks/useExpression.ts:132), where context is the row spread at the top level. So only the shorthand spelling resolves:
predicate
verdict
status == "pending"
evaluates
record.status == "pending"
record is not defined → throws → fail-closed → hidden
record.* is not a mistaken spelling — it is the canonical one:
ExpressionEvaluator.evaluateCelCondition binds it explicitly: "the record key as the record namespace and the whole context bag as top-level scope" (packages/core/src/evaluator/ExpressionEvaluator.ts:276-291);
the record header and list rows bind the row three ways through evalRowPredicate — record.status, bare status, data.status (packages/components/src/renderers/layout/containers.tsx:1074-1082);
the server enforces with it.
The renderers that bind only the bare root:
renderer
line
action-button.tsx
:59 (visible), :69 (disabled), :70 (enabled)
action-icon.tsx
:46, :50, :51
action-menu.tsx / action-group.tsx
pass no record context at all
Measured (probe against the shipped evaluator, throwOnError: true, row { id, status, viewer: { can_act: true } }):
ROOT-CONTEXT => threw: true, 'record is not defined'
WITH-RECORD-KEY => threw: false, true
Why it is worth fixing rather than noting
This was live, not theoretical. DeclaredActionsBar had the same root-only binding, and every declared action on framework sys_approval_request gates on record.viewer.can_act / record.viewer.is_submitter (framework#3310 / #3424) — so the entire server-declared approval decision set evaluated to "hidden" on every surface that bar renders, the approvals inbox included. objectui#3055's PR fixes it there by binding the row all three ways; the four renderers above still carry the original binding.
The failure is silent in the worst direction: fail-closed means the button simply is not there, which is indistinguishable from "the gate said no".
Suggested fix (left to triage, not self-selected)
Bind the record the three canonical ways in each renderer's predicate context (what objectui#3055's PR does for the declared-action bar), or
move the four renderers onto evalRowPredicate, the helper the header already uses, so there is one binding rule rather than two.
Leaning 2 — one implementation behind one name is the standing preference in this area (objectui#3367, objectui#3842) — but 1 is the smaller step and does not disturb the objectui#3492 family's pinned evaluation entry.
Dedup
Searched open issues in this repo for visible predicate record. root, declared action visible record.viewer, predicate scope record root, action-button visible bare field binding useCondition. One neighbour, not a duplicate: objectui#3888 is about an ADR paragraph asserting fail-open for server enforcement, whose unbound-root counter-example is the same family of fault but a documentation fix in another repo's rule validator. No issue covers the client renderers' binding.
Triage + release board (objectui seat PM — this repo's board producer): pm:queue + target:v17, class ② — a predicate written in the canonical spelling (record.*, the one the evaluator documents, the header/list rows bind, and the server enforces with) is silently dishonored by four shipped action renderers, fail-closed into "hidden". The declared-actions bar instance of this exact fault suppressed the entire server-declared approval decision set on every surface until PR #4077; the four generic renderers still carry the original binding. An authoring surface that punishes the correct spelling is not shippable as-is.
Claim: PM loop round 3 (objectui whole-repo seat, v17 board priority)
Session: session_017Qqyix2QcnpUC9XeYVDzx3
Branch: claude/issue-4075-action-predicate-binding
Worktree: objectui-issue-4075
Domain: repo:objectui (whole-repo seat)
File surface: packages/components/src/renderers/actions/ (action-button / action-icon / action-menu / action-group predicate context) — or the shared helper route touching evalRowPredicate consumers; plus tests. ⛔ Region-exclusive vs in-flight #4042 (app-shell providers/HTTP logging) — disjoint packages. Note containers.tsx was touched by the merged #4070; work from current origin/main.
Container & model: M, mode:subagent, model: opus
Serial constraints cleared: #3055 / PR #4077 MERGED (the DeclaredActionsBar half of this family); no open claim or PR touches the four action renderers.
What shipped: all four action renderers bind the row the three canonical ways through one named helper (usePredicateRecordContext in @object-ui/react, beside useCondition); action:icon now reads its data prop at all (it evaluated against an empty bag and leaked data into the DOM spread), action:menu/action:group leaves take the row from their host, and action:bar forwards the row into its overflow menu (a predicate had been answering a different question purely because the action spilled past maxVisible, which defaults to 1 on mobile). Changeset patch on components + react.
Review notes: route 1 correctly chosen over route 2 with the reasoning recorded — useCondition and evalRowPredicate are different evaluation entries pinned by four suites (#3850/#3871/#3812/#3842-#3849), so unification would have rewritten their subject; the one-implementation preference was honored by naming the binding rule once. The natural-direction reverse verification (suite written pre-fix: 30 red / 38 green, polarity per site explained by each site's error policy) is the strongest form. A first-cut regression (empty record shadowing the ambient PredicateScopeProvider) was caught by existing tests and resolved to "no row → bind nothing", both directions pinned. Per-site error policies (fail-soft on icon/group visible per #3871's table) preserved and pinned rather than converted — correct scope discipline. 519 files / 4979 tests green post-merge with main; all 20 checks completed, 0 failures (Lint, Type Check, 4 shards individually verified). Finding #4080 (DeclaredActionsBar's inline copy of the now-named binding rule) verified filed as observation-class.
Landing next: flipping ready and driving to MERGED.
Activity
yinlianghui commentedon Aug 10, 2026
Triage + release board (objectui seat PM — this repo's board producer):
pm:queue+target:v17, class ② — a predicate written in the canonical spelling (record.*, the one the evaluator documents, the header/list rows bind, and the server enforces with) is silently dishonored by four shipped action renderers, fail-closed into "hidden". The declared-actions bar instance of this exact fault suppressed the entire server-declared approval decision set on every surface until PR #4077; the four generic renderers still carry the original binding. An authoring surface that punishes the correct spelling is not shippable as-is.Claim: PM loop round 3 (objectui whole-repo seat, v17 board priority)
Session:
session_017Qqyix2QcnpUC9XeYVDzx3Branch:
claude/issue-4075-action-predicate-bindingWorktree:
objectui-issue-4075Domain:
repo:objectui(whole-repo seat)File surface:
packages/components/src/renderers/actions/(action-button / action-icon / action-menu / action-group predicate context) — or the shared helper route touchingevalRowPredicateconsumers; plus tests. ⛔ Region-exclusive vs in-flight #4042 (app-shell providers/HTTP logging) — disjoint packages. Notecontainers.tsxwas touched by the merged #4070; work from currentorigin/main.Container & model: M,
mode:subagent,model: opusSerial constraints cleared: #3055 / PR #4077 MERGED (the DeclaredActionsBar half of this family); no open claim or PR touches the four action renderers.
Generated by Claude Code
yinlianghui commentedon Aug 10, 2026
ACCEPT (objectui seat PM, session
session_017Qqyix2QcnpUC9XeYVDzx3, review of record) — PR #4079.What shipped: all four action renderers bind the row the three canonical ways through one named helper (
usePredicateRecordContextin@object-ui/react, besideuseCondition);action:iconnow reads itsdataprop at all (it evaluated against an empty bag and leakeddatainto the DOM spread),action:menu/action:groupleaves take the row from their host, andaction:barforwards the row into its overflow menu (a predicate had been answering a different question purely because the action spilled pastmaxVisible, which defaults to 1 on mobile). Changeset patch on components + react.Review notes: route 1 correctly chosen over route 2 with the reasoning recorded —
useConditionandevalRowPredicateare different evaluation entries pinned by four suites (#3850/#3871/#3812/#3842-#3849), so unification would have rewritten their subject; the one-implementation preference was honored by naming the binding rule once. The natural-direction reverse verification (suite written pre-fix: 30 red / 38 green, polarity per site explained by each site's error policy) is the strongest form. A first-cut regression (empty record shadowing the ambientPredicateScopeProvider) was caught by existing tests and resolved to "no row → bind nothing", both directions pinned. Per-site error policies (fail-soft on icon/groupvisibleper #3871's table) preserved and pinned rather than converted — correct scope discipline. 519 files / 4979 tests green post-merge with main; all 20 checks completed, 0 failures (Lint, Type Check, 4 shards individually verified). Finding #4080 (DeclaredActionsBar's inline copy of the now-named binding rule) verified filed as observation-class.Landing next: flipping ready and driving to MERGED.
Generated by Claude Code
fix(components): bind the row three ways on the four action renderers (…