Skip to content

docs(spec,lint): a form FIELD visibleWhen binds current_user — re-measure the three stale text sites - #12930

Merged
os-trump merged 2 commits into
mainfrom
claude/issue-11813-visiblewhen-binding-text
Aug 28, 2026
Merged

docs(spec,lint): a form FIELD visibleWhen binds current_user — re-measure the three stale text sites#12930
os-trump merged 2 commits into
mainfrom
claude/issue-11813-visiblewhen-binding-text

Conversation

@os-trump

Copy link
Copy Markdown
Collaborator

Fixes#11813

objectui#6010 bound the host predicate scope on the form renderer's authored-predicate call sites, so a runtime form FIELDvisibleWhen / visibleOn now resolves current_user and the ADR-0068 D1 aliases user / ctx.user / os.user. Three texts shipped by this repo still asserted the opposite, and that per-option visibleWhen was "the only *When surface where it resolves".

Documentation carried inside code. No schema, validation or verdict change: check:authorable-surface is green (the legal metadata set is byte-identical) and FIELD_RULE_BOUND_ROOTS is untouched.

What changed

Site 1 — packages/spec/src/ui/view.zod.ts, FormFieldSchema.visibleWhen. The doc block and the describe() now state the binding, together with the two limits it does not remove — both of which fail in a direction an author does not notice:

  1. It is a rendering rule, never authorization. Nothing on the write path evaluates a form-view field visibleWhen. packages/objectql/src/validation/rule-validator.ts evaluates field readonlyWhen / requiredWhen and per-option visibleWhen, and that is the whole list — so a role test written here hides a control and protects no data.
  2. The scope belongs to the HOST, so it is empty where no host publishes one. The console's public form route (/f/:slug) is mounted outside any provider deliberately — an anonymous visitor has no principal — so the root is unbound there, the predicate faults, and visibility's fallback is true. The authed standalone route (/forms/:name) publishes the session principal and binds normally.

Site 2 — content/docs/references/ui/view.mdx. Regenerated, never hand-edited (pnpm --filter @objectstack/spec gen:docs). The diff is exactly two lines: the FormField.visibleWhen row at :163 and the same row nested under FormSection.fields[number] at :325. The three FormSection.visibleWhen rows are untouched — see the fence below.

Site 3 — packages/lint. The fieldRuleRootIssue prescription no longer grounds "move it to the option's own visibleWhen" on exclusivity; it grounds it on enforcement — the rule validator evaluates a per-option predicate on every write — and names the form-view field predicate only to refuse it as a destination for a server-enforced object rule, since moving one there would trade a loud lint error for a silent enforcement gap. The gate script's layer table splits the folded cell (object field / form section was carrying the view form field layer's verdict as well) and is re-measured at objectui f241a4d.

The section slot was re-measured, and its verdict HAS changed — filed, not swept

The card and the dispatch both fenced FormSectionSchema.visibleWhen out of this PR as a different verdict, conditional on a re-measurement. The re-measurement was done and the fenced premise is falsified: both halves it rested on have since landed. The section text is therefore left byte-identical here, and the evidence is filed as #12914 rather than acted on — reporting it is what the fence asks for; sweeping it is what the fence forbids.

  • objectui#6110 landed.apps/console/src/components/FormPage.tsx now reads usePredicateScope() (:1568) and threads it into all three evaluators. isSectionVisible takes a predicateScope parameter (:648-660) and is called with it at :1849. The card measured it passing undefined.
  • objectui#6111 landed.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 predicate with predicateScope bound (:1296-1311, :2179-2204). There is a dedicated pin, sectionVisibleWhen-6111.test.tsx.

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

Measured in the objectui checkout at origin/main @ f241a4d; every path above is byte-identical at the local checkout efdc6c6 (git diff --stat HEAD origin/main over those paths shows one unrelated test fixture and nothing else).

#12914 also records three further stale sites this card's fences exclude: data/field.zod.ts's SelectOptionSchema.visibleWhen, shared/editability-boundary.ts's option prescription, and the visibleWhen consequence cell in validate-expressions.ts whose renderer half moved with objectui#6010. None of them is touched here. #12914 remains open.

Pins

git grep over the retired sentences found one pin: packages/lint/src/validate-expressions.test.ts, whose comment at the prescription assertion quoted "the one *When surface that binds current_user". It moves in this PR, and its three existing assertions (option's own visibleWhen, readable: false, falls back to VISIBLE) are unchanged and still pass. Two assertions are added: the message names the rule validator grounding, and the retired exclusivity claim cannot come back.

Verification

Union re-run at the final commit a2c680686 (working tree identical to HEAD), all exit codes captured before any pipe:

  • pnpm --filter @objectstack/spec check:generated"All 14 generated artifacts are up to date", including check:docs and check:authorable-surface.
  • pnpm --filter @objectstack/spec run check:liveness — green, and it asserts "every path:NNN citation names a line that file actually has", which is the anchor-drift risk a doc-block edit in view.zod.ts carries.
  • check:strictness-ledger, check:empty-state, check:variant-docs, check:type-check-coverage, check:query-options-erasure, check:where-matcher, check:engine-double-contract, check:nul-bytes, check:doc-authoring, check:merge-driver — all exit 0.
  • pnpm --filter @objectstack/lint run check:doc-formula-expressions"self-test: 57 cases passed", then the corpus run green.
  • pnpm --filter @objectstack/lint run check:doc-security-posture — green.
  • pnpm --filter @objectstack/lint test — 81 files, 2295 passed, 5 skipped. pnpm --filter @objectstack/spec test — 440 files, 11646 passed.
  • pnpm --filter @objectstack/lint typecheck and pnpm --filter @objectstack/spec typecheck — clean. Note the lint package's tsconfig.json excludes **/*.test.ts, verified with tsc --noEmit --listFiles (0 hits for the edited test file), so that green says nothing about it; its coverage comes from the vitest run above.
  • pnpm lint (repo-wide eslint . --no-inline-config) — exit 0. Run in full rather than narrowed.
  • Gate families re-derived from the real change set with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, not from the dispatch list, and everything it named was run except as noted below.

Not measured, with reasons: check:type-check-debt --re-measure needs the whole workspace built and is CI's run; scripts/pm/check-half-states.mjs exits 3 (PREREQUISITE NOT MET — this container has no GitHub credential), which its own text calls "no reading at all", and it is unrelated to this diff; check:react-declaration-parity cannot run in this repo at all (it needs objectui's sdui.manifest.json).

Landing

Draft on purpose. The diff touches packages/spec/src/**, so the review chain owns enqueue — do not flip ready and do not arm auto-merge from here.


Generated by Claude Code

…-measure the three stale text sites
objectui#6010 bound the host predicate scope on the form renderer's authored-
predicate call sites, so a runtime form FIELD `visibleWhen` / `visibleOn` now
resolves `current_user` and the ADR-0068 D1 aliases. Three texts shipped by this
repo still asserted the opposite, and that per-option `visibleWhen` was "the only
`*When` surface where it resolves".
- `packages/spec/src/ui/view.zod.ts`: `FormFieldSchema.visibleWhen`'s doc block
and its `describe()` state the binding plus the two limits it does not remove —
it is a rendering rule nothing on the write path evaluates, and the scope is
the host's, so it is empty (and fails open) on the public standalone form route.
- `content/docs/references/ui/view.mdx`: regenerated from the above, never
hand-edited (`gen:docs`; two `FormField` rows).
- `packages/lint`: the field-rule prescription is re-grounded on ENFORCEMENT —
the rule validator evaluates a per-option predicate on every write — instead of
on exclusivity, and names the form-view field predicate only to refuse it as a
destination for a server-enforced object rule. The gate script's layer table
splits the folded cell and is re-measured at objectui `f241a4d`.
The `FormSectionSchema.visibleWhen` slot is deliberately NOT swept: its verdict
was re-measured and has changed too (objectui#6110 / objectui#6111 both landed),
which is a different card — the evidence and three further stale sites are
recorded in the follow-up issue rather than fixed here.
No schema, validation or verdict change: `check:authorable-surface` is green and
`FIELD_RULE_BOUND_ROOTS` is untouched.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/lint, @objectstack/spec, touching 4 documentable anchor(s).

6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/protocol/objectui/actions.mdx(via /f/:slug (route), /forms/:name (route))
  • content/docs/protocol/objectui/layout-dsl.mdx(via /f/:slug (route), /forms/:name (route))
  • content/docs/ui/actions.mdx(via /forms/:name (route))
  • content/docs/ui/forms.mdx(via /f/:slug (route), /forms/:name (route))
  • content/docs/ui/public-data-collection.mdx(via /forms/:name (route))
  • content/docs/ui/views.mdx(via /f/:slug (route), /forms/:name (route))
What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 126 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 750fff55921e1e4a6906cbe30c22ec87cf94350bpackageMentionDocs.

Which tree this was computed on

This run read content/docs from e473b945ed0389383bdff5c9b10304a3521695c9 — the merge of head a2c6806865edd91cc5c40e5cf9acbf899888041c into base 750fff55921e1e4a6906cbe30c22ec87cf94350b, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin e473b945ed0389383bdff5c9b10304a3521695c9 && git checkout e473b945ed0389383bdff5c9b10304a3521695c9
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 750fff55921e1e4a6906cbe30c22ec87cf94350b a2c6806865edd91cc5c40e5cf9acbf899888041c && git checkout -B drift-repro 750fff55921e1e4a6906cbe30c22ec87cf94350b && git merge --no-ff a2c6806865edd91cc5c40e5cf9acbf899888041c
node scripts/docs-audit/affected-docs.mjs --json 750fff55921e1e4a6906cbe30c22ec87cf94350b

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 750fff55921e1e4a6906cbe30c22ec87cf94350b → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation protocol:ui tests tooling labels Aug 28, 2026
@os-trump
os-trump marked this pull request as ready for review August 28, 2026 12:46
@os-trump
os-trump enabled auto-merge August 28, 2026 12:46
@os-trump
os-trump added this pull request to the merge queueAug 28, 2026
Merged via the queue into main with commit b5a2398Aug 28, 2026
41 checks passed
@os-trump
os-trump deleted the claude/issue-11813-visiblewhen-binding-text branch August 28, 2026 13:31
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationprotocol:uisize/mteststooling

Projects

None yet

2 participants

@os-trump@claude