Skip to content

Four more current_user binding-text sites are stale — including the form SECTION slot, whose verdict flipped when objectui#6110 / #6111 landed #12914

Description

@os-trump

Filed unassigned by the dev seat implementing #11813, whose dispatch fenced every one of these out of that PR. Recording only — not graded, no domain:*.

Measured on this repo origin/main @ aef1b7e6 and objectui origin/main @ f241a4d (the objectui files named below are byte-identical at the local checkout efdc6c6, verified with git diff --stat HEAD origin/main over those paths — only a test fixture differs).

1. The form SECTION slot — the verdict the #11813 fence protected has since changed

#11813's body and its dispatch both fenced FormSectionSchema.visibleWhen (packages/spec/src/ui/view.zod.ts, the JSDoc above visibleWhen plus its describe()) out of scope, on a measurement taken at objectui a100f77: the only reader of an authored section predicate passed undefined for the scope, and the object-view chain dropped the key before it reached a renderer that would bind one. Both halves of that are now discharged, and the re-measurement is the deliverable here — the #11813 PR deliberately left the section text untouched rather than sweep it.

  • objectui#6110 landed.apps/console/src/components/FormPage.tsx now reads usePredicateScope() (declared at :1568, docblock :1543-1567) and threads it into all three evaluators. isSectionVisible takes a predicateScope parameter (:648-660) and is called with it at :1849 — isSectionVisible(sec, values, loaded.record, predicateScope). It was undefined.
  • objectui#6111 landed. The object-view chain no longer drops the key: packages/plugin-form/src/ObjectForm.tsx (:1222), SplitForm.tsx (:386), ModalForm.tsx (:721) and DrawerForm.tsx (:597) copy the authored section.visibleWhen onto the section-divider pseudo-field, and packages/components/src/renderers/form/form.tsx evaluates that pseudo-field's predicate with predicateScope bound (:1296-1311 for the group verdict, :2179-2204 for the heading row). There is a dedicated pin, packages/plugin-form/src/__tests__/sectionVisibleWhen-6111.test.tsx.

usePredicateScope() resolves to buildExpressionScope() in packages/app-shell/src/providers/ExpressionProvider.tsx:82-89, which returns current_user plus the ADR-0068 D1 aliases user / ctx.user / os.user, and app / data / features.

So the section describe()'s "No current_user at section level — it is unbound here and the predicate would fault open" is now false wherever a host publishes a scope, and its JSDoc's "as there, no current_user: it is unbound at this level" inherits the same staleness. The two qualifications #11813's PR wrote into the FIELD text apply verbatim here: the binding is CLIENT-SIDE only (nothing on the write path evaluates a form-view section or field visibleWhenpackages/objectql/src/validation/rule-validator.ts evaluates field readonlyWhen / requiredWhen and per-option visibleWhen, and that is the whole list), and it is empty on the public /f/:slug route, which is mounted outside any provider on purpose (FormPage.tsx:1551-1561).

⚠️ Fixing this is NOT a re-run of #11813's edit with the section identifier substituted. The features.* refusal sentence in the same string stays exactly as it is — that root is unbound on BOTH standalone routes, because InternalFormRoute publishes only user (ExpressionProvider user={expressionUser}, apps/console/src/components/InternalFormRoute.tsx:81), never features.

2. packages/spec/src/data/field.zod.tsSelectOptionSchema.visibleWhen

The JSDoc says field- and section-level rules "go through evalFieldPredicate, which binds record + previous + parent and never current_user (objectui#1582). Per-option is the one *When surface where a current_user test actually resolves." The describe() on the same key carries the short form: "wider than field-level visibleWhen, which has no current_user". Both are the #11813 claim in another file. The paragraph's real point — the option gate is the one the SERVER enforces on write — survives and is worth keeping as the grounding; the exclusivity is what aged out.

3. packages/spec/src/shared/editability-boundary.tsSELECT_OPTION_EDITABILITY_GUIDANCE

The prescription tells an author to use the per-option predicate, "the one *When surface that also binds current_user (ADR-0068)". Same retired claim, and this one is an author-facing parse-rejection message rather than a doc block. Its next clause ("the rule validator refuses a write of a value whose predicate is false") is already the durable grounding, so the fix is a trim, not a rewrite. Note editability-boundary.test.ts pins this table.

4. packages/lint/src/validate-expressions.ts — the visibleWhen consequence cell

FIELD_RULE_SLOT_CONSEQUENCE.visibleWhen states that a user root here means "the predicate faults and the renderer falls back to VISIBLE". The server half of that cell is still exactly right (no server-side gate evaluates a field-level visibleWhen), but the RENDERER half moved: packages/plugin-form/src/sectionFields.ts copies the object field's ADR-0036 visibleWhen / readonlyWhen / requiredWhen straight onto the runtime form field (fromObjectSchema), and form.tsx:2179-2204 resolves those through resolveFieldRuleStatewith predicateScope bound. So on a host that publishes a scope the predicate no longer faults — it resolves, and the field is hidden client-side while the server still returns the value to every other reader.

That makes the rule's refusal MORE right, not less: the failure is now a silent enforcement gap rather than a visible fail-open, which is the worse of the two. But the sentence describing the mechanism should say so. ⛔ Whoever takes this must not read it as a reason to relax FIELD_RULE_BOUND_ROOTS — the verdict stays; only the causal clause is stale. #11813's PR left this cell alone deliberately, and its pin (validate-expressions.test.ts, expect(issue!.message).toMatch(/falls back to VISIBLE/)) moves with whatever wording replaces it.

Suggested shape

Sites 2-4 are one-line trims of a retired exclusivity claim and could ride together. Site 1 is the substantive one: it is a re-measurement with a fence history, it regenerates content/docs/references/ui/view.mdx (the FormSection row), and it deserves its own reading of what the section text should now say. Whether that is one PR or two is a triage call.

Pointers

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions