Repository navigation
[a11y] The RLS USING/CHECK predicate editors render a label bound to nothing — CelPredicateField's id is optional and three call sites omit it #4533
Description
Activity
Triage:
pm:queue,type: Bug. Mechanical a11y fix with the guilty call sites, the correct-shape sibling (ObjectFieldInspector), and a component-level fix direction already named (React.useId()fallback so a call site can't omit the association) — no design decision pending, same family as the already-fixed #3994/#3341.Size/model suggestion: S,
mode:subagent,model: sonnet. Prefer the component-level fallback over patching all three call sites individually — it's the "hard to get wrong" direction the filer names, and it structurally prevents a fourth call site from reintroducing this.
Generated by Claude Code
CLAIM — session_017Qqyix2QcnpUC9XeYVDzx3, branch
claude/issue-4533-cel-field-label-binding. Dispatching a dev agent now.PM ruling (delegated decision authority; maintainer veto window open — record objections here): fix the CONTRACT so the accessibility hole cannot recur, not just the three call sites:
CelPredicateFieldgenerates a fallback id via React'suseId()when theidprop is omitted, and uses it for BOTHLabel htmlForandTextarea id— so every mount binds its label, including future call sites that forget the prop. The explicitidprop keeps winning when passed (pin ObjectFieldInspector's four explicit-id call sites unchanged).- Whether to ALSO pass explicit ids at the three omitting call sites (PermissionAdvancedFacets RLS USING :356 / CHECK :372, ConditionalFormattingEditor :272): only if measurement shows a concrete need (e.g. stable ids asserted by existing tests); otherwise the useId fallback IS the fix — fewer moving parts.
- Red-first with the accessibility query itself:
getByLabelText('USING (read filter)')(the exact query [security] An RLS policy authored in Studio's package door is silently discarded — Save succeeds, no row filter is ever persisted #4302's dev found refused) fails on current main and passes post-fix, for all three omitting sites. Pin: explicit-id sites keep their exact ids; fix(app-shell): package-door permission save carries every editor-authorable facet (#4302) #4534's placeholder-based selection keeps working (do not edit that test — its comment already points here). - Changeset: '@object-ui/app-shell': patch expected (CelPredicateField is module-local unless measurement says otherwise — verify via .d.ts both ways). Never major.
Mutual exclusion: ⛔ #4527-phase-2 is IN FLIGHT and owns the default-inspector family + hosts (ResourceEditPage, StudioDesignSurface, ObjectSettings/Actions/Hooks panels, ObjectValidationsPanel, Hook/Action/ViewVariant inspectors, inspector-registry types). CelPredicateField.tsx, PermissionAdvancedFacets.tsx and ConditionalFormattingEditor.tsx are NOT in its write set — but if your work needs ANY file in that list, STOP and report. Disjoint from #4541 (plugin-grid), #4553 (plugin-gantt), #4254 (plugin-kanban).
Generated by Claude Code
Generated by Claude Code
Record correction (PM, session_017Qqyix2QcnpUC9XeYVDzx3), so no future reader chases a false coordinate: this issue's table lists ConditionalFormattingEditor.tsx:272 as an omitting call site. Measured false — that mount has passed
id={cf-condition-${i}}since its original commit (fba6875, #2558); the :272 reading was the opening-tag line of a multi-line JSX element whoseidprop sits at :287, and four pre-existing getElementById('cf-condition-0') assertions could not have passed otherwise. The REAL third omitting site was inspectors/ConditionBuilder.tsx:227, which the table did not list. PR #4561's component-level useId fallback repairs all three real sites (RLS USING, RLS CHECK, inspectors/ConditionBuilder) without editing any call site; the corrected table is in that PR's body.
Generated by Claude Code
Generated by Claude Code
- added a commit that references this issue
on Aug 17, 2026
What was measured
CelPredicateFieldaccepts an optionalidprop and uses it for BOTH halves of the association:When a call site omits
id, both becomeundefined, so the visible label is bound to no control: the textarea has no accessible name, and the label is orphaned text.Three of the four call-site groups omit it:
id?inspectors/ObjectFieldInspector.tsx:478 / :615 / :628 / (formula)id={"field-rule-visible-…"}etc.PermissionAdvancedFacets.tsx:356 (RLS USING), :372 (RLS CHECK)ConditionalFormattingEditor.tsx:272So the RLS policy editor's USING and CHECK predicate fields — the row-filter authoring surface of a permission set — and the conditional-formatting rule editor render a label that reads correctly on screen and resolves to nothing for a screen reader.
ObjectFieldInspectorshows the intended shape: the prop exists and is used correctly there.How it surfaced
Writing the wire pins for objectui#4302 I reached for the obvious selector and testing-library refused it verbatim:
The test now selects that textarea by placeholder instead, with a comment pointing here. That is a workaround in a test, not a fix.
Why this is filed rather than fixed
Out of scope for objectui#4302, whose ruling limits the surface to the package-door slice merge and its pins. Same family as the already-fixed objectui#3994 (metadata-admin inspectors' shared field components had
Labelnext to the control without associating them) and objectui#3341 — this is the residue those two did not reach.Suggested shape
Pass an
idat the three call sites (the RLS facet has a policy index available:rls-{i}-using/rls-{i}-check), or haveCelPredicateFieldfall back to a generated id (React.useId()) when the prop is absent, so a call site cannot silently produce an unlabelled control. The second is the "hard to get wrong" direction — the label is rendered by the component itself, so the component can guarantee the association rather than trusting every caller to remember.Unassigned, for triage.
Generated by Claude Code