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.
A ${…} visibility predicate written inside properties is interpolated to a boolean before the visibility chain sees it, so no node-gate diagnostic can reach it #5756
Measured while implementing #5687 (making a node-gate data.* predicate the adapter cannot answer emit a dev-only report). It is a different silence in the same chain, deliberately left alone there because the 2026-08-22 ruling on #5687 fences off interpolation ("no interpolation changes"). Filed unassigned for triage.
Duplicate search run before filing: semantic + keyword sweep of open issues over the properties hoist, the interpolation ordering, and the visibility-diagnostic surface (#5454, #5401, #4051, #3067, #5399) found nothing covering this.
Facts (measured on origin/main3ece13e, plus @object-ui/core's built evaluator)
packages/react/src/SchemaRenderer.tsx's evaluation memo evaluates every properties.* value before the hoist that copies them onto the node. That ordering is deliberate and documented in place — "Evaluating first is what makes one key mean one thing whether it is read as schema.properties.x, as schema.x, or as the x prop."
The second row hides the block on every row exactly like the first, and the author gets no signal at all. Pinned as a known limit in packages/react/src/__tests__/SchemaRenderer.nodeGateDataPredicate.test.tsx ("KNOWN LIMIT, measured and pinned"), which asserts the verdict and the silence rather than papering over either.
Why this is not just a data.* problem
The pre-interpolation is spelling-agnostic. Any predicate written as a ${…} template inside properties arrives at the chain as an already-collapsed value, so no diagnostic that inspects the predicate at the gate — #5454's unresolvable-predicate reporter included — can ever fire for it. A record.* typo written as properties: { visible: "${record.staus == 'draft'}" } is in the same position.
This matters because ${…} is not an exotic spelling. @object-ui/types' own protocol comments use it for exactly these keys:
Reaching it means adding a diagnostic inside the interpolation loop, gated on the key being one of the visibility keys. That is a small change, but it sits directly on the surface the #5687 ruling fenced off, and it needs a decision this card cannot take for itself:
the same loop carries genuine interpolations (properties: { content: '${data.total}' }) that must stay silent, so the check has to be keyed on the visibility key set rather than on the value.
Neither is hard; both are choices, not mechanics.
Reproduction
packages/react/src/__tests__/SchemaRenderer.nodeGateDataPredicate.test.tsx, case "KNOWN LIMIT, measured and pinned: a ${…} template INSIDE properties is invisible to this leg" — mounts the template spelling on two different rows, asserts the block is hidden on both, and asserts zero diagnostics. Flipping the expected report count from 0 to 1 is the failing reproduction.
Triage: pm:queue, domain:ui, Task — a ${…}-spelled visibility predicate collapses to a boolean before the chain, so the #5454/#5687 diagnostic family can never see it; the KNOWN-LIMIT pin already names the failing repro. Scope ruling carried with the card: the 2026-08-22 "no interpolation changes" fence on #5687 was that card's scope constraint, not a standing ban — this card is the deliberate carrier for that surface, and the constraint that DOES stand is: diagnostic only, interpolation results must not change (the evaluation memo's documented one-key-one-meaning ordering stays). The two design points the card names are the dev's to settle with stated rationale in the PR body, not escalation material: key the check on the visibility key set (the card's own analysis), and pick a defensible posture on the outranked-leg false positive — mirroring #5454's leg semantics if its reporter already solved this. Genuine interpolations (content: '${data.total}') must stay silent — pin that too.
Dispatching this round (R27). ⚠️ The scope ruling triage carried is the load-bearing part and is repeated in the brief: the 2026-08-22 「no interpolation changes」 fence was #5687's own scope constraint, not a standing ban — this card is the deliberate carrier for that surface. What DOES stand: diagnostic only; interpolation results must not change.
{
"issue": 5756,
"status": "done",
"branch": "claude/issue-5756-properties-predicate-diagnostic",
"pr": "https://github.com/objectstack-ai/objectui/pull/5820",
"premise_still_valid": true,
"summary": "Confirmed the PM's mechanism hypothesis: `SchemaRenderer`'s `properties.*` evaluation loop interpolates every `${…}` template before the visibility chain sees it, so a template-spelled predicate arrives at the gate already collapsed to a boolean with no text left to diagnose. Fixed by moving `evaluateVisibilityPredicate` earlier in the render memo and adding a second diagnostic-only call site inside the properties loop, on the raw pre-interpolation text of whichever visibility key actually WINS the precedence chain (a new `winningVisibilityKey` helper, computed post-properties-eval/pre-hoist so its declaredness answers agree bit-for-bit with `shouldHide`'s later ones). Reports only the winning key (mirrors #5454's own leg semantics, so an outranked `properties.visible` template does not get reported for deciding nothing) and only visibility keys (a `properties.content` interpolation stays untouched). No verdict change, no interpolation change: the diagnostic call's return value is discarded.",
"tests": "At 212e14a37 (HEAD): `pnpm exec vitest run packages/react/src/__tests__/SchemaRenderer.nodeGateDataPredicate.test.tsx` -> 23/23 passed (18 pre-existing, one flipped 0->1 report as the card predicted, one production case extended; 5 new in a `#5756 group 5` block covering the two named design points plus a value-level interpolation-unchanged control). `pnpm exec vitest run packages/react/ --maxWorkers=2` -> 711/711 passed, no regressions. `pnpm --filter '@object-ui/react^...' build` clean (dependency closure). `pnpm --filter '@object-ui/react' type-check` (tsc --noEmit + tsconfig.test.json) clean. `pnpm --filter '@object-ui/react' build` (tsc) clean. `pnpm exec eslint <2 touched files> --no-inline-config` -> 0 errors, 19 pre-existing any-warnings, diff adds zero new `any` (verified via `git diff origin/main | grep any`). `node scripts/check-control-bytes.mjs`, `check-changeset-presence.mjs`, `check-changeset-no-major.mjs` all OK; changeset declared as `@object-ui/react: patch`. Reverse verification: disabled the new call site (`if (false && __DEV__)`), reran the pinned file -> exactly the 3 tests asserting a NEW positive report turned red (flipped KNOWN-LIMIT test, hidden-leg 5c, value-control 5e); the negative/control tests (outranked-leg 5a, genuine-read-silent 5b, unresolvable-goes-to-other-reporter 5d) stayed green since they assert silence regardless. Restored; `git diff` against the committed fix came back empty (byte-identical round trip).",
"open_questions": [],
"out_of_scope_findings": []
}
Activity
os-zhuang commentedon Aug 23, 2026
Triage:
pm:queue,domain:ui, Task — a${…}-spelled visibility predicate collapses to a boolean before the chain, so the #5454/#5687 diagnostic family can never see it; the KNOWN-LIMIT pin already names the failing repro. Scope ruling carried with the card: the 2026-08-22 "no interpolation changes" fence on #5687 was that card's scope constraint, not a standing ban — this card is the deliberate carrier for that surface, and the constraint that DOES stand is: diagnostic only, interpolation results must not change (the evaluation memo's documented one-key-one-meaning ordering stays). The two design points the card names are the dev's to settle with stated rationale in the PR body, not escalation material: key the check on the visibility key set (the card's own analysis), and pick a defensible posture on the outranked-leg false positive — mirroring #5454's leg semantics if its reporter already solved this. Genuine interpolations (content: '${data.total}') must stay silent — pin that too.Generated by Claude Code
claude commentedon Aug 23, 2026
Claim —
domain:uiexecution seat.session_01EuPCi56cnGyykygi3z9w4mclaude/issue-5756-properties-predicate-diagnosticDispatching this round (R27).⚠️ The scope ruling triage carried is the load-bearing part and is repeated in the brief: the 2026-08-22 「no interpolation changes」 fence was #5687's own scope constraint, not a standing ban — this card is the deliberate carrier for that surface. What DOES stand: diagnostic only; interpolation results must not change.
Generated by Claude Code
fix(react): report a `${…}` visibility predicate collapsed inside `pr…
os-zhuang commentedon Aug 23, 2026
os-dev-report
{ "issue": 5756, "status": "done", "branch": "claude/issue-5756-properties-predicate-diagnostic", "pr": "https://github.com/objectstack-ai/objectui/pull/5820", "premise_still_valid": true, "summary": "Confirmed the PM's mechanism hypothesis: `SchemaRenderer`'s `properties.*` evaluation loop interpolates every `${…}` template before the visibility chain sees it, so a template-spelled predicate arrives at the gate already collapsed to a boolean with no text left to diagnose. Fixed by moving `evaluateVisibilityPredicate` earlier in the render memo and adding a second diagnostic-only call site inside the properties loop, on the raw pre-interpolation text of whichever visibility key actually WINS the precedence chain (a new `winningVisibilityKey` helper, computed post-properties-eval/pre-hoist so its declaredness answers agree bit-for-bit with `shouldHide`'s later ones). Reports only the winning key (mirrors #5454's own leg semantics, so an outranked `properties.visible` template does not get reported for deciding nothing) and only visibility keys (a `properties.content` interpolation stays untouched). No verdict change, no interpolation change: the diagnostic call's return value is discarded.", "tests": "At 212e14a37 (HEAD): `pnpm exec vitest run packages/react/src/__tests__/SchemaRenderer.nodeGateDataPredicate.test.tsx` -> 23/23 passed (18 pre-existing, one flipped 0->1 report as the card predicted, one production case extended; 5 new in a `#5756 group 5` block covering the two named design points plus a value-level interpolation-unchanged control). `pnpm exec vitest run packages/react/ --maxWorkers=2` -> 711/711 passed, no regressions. `pnpm --filter '@object-ui/react^...' build` clean (dependency closure). `pnpm --filter '@object-ui/react' type-check` (tsc --noEmit + tsconfig.test.json) clean. `pnpm --filter '@object-ui/react' build` (tsc) clean. `pnpm exec eslint <2 touched files> --no-inline-config` -> 0 errors, 19 pre-existing any-warnings, diff adds zero new `any` (verified via `git diff origin/main | grep any`). `node scripts/check-control-bytes.mjs`, `check-changeset-presence.mjs`, `check-changeset-no-major.mjs` all OK; changeset declared as `@object-ui/react: patch`. Reverse verification: disabled the new call site (`if (false && __DEV__)`), reran the pinned file -> exactly the 3 tests asserting a NEW positive report turned red (flipped KNOWN-LIMIT test, hidden-leg 5c, value-control 5e); the negative/control tests (outranked-leg 5a, genuine-read-silent 5b, unresolvable-goes-to-other-reporter 5d) stayed green since they assert silence regardless. Restored; `git diff` against the committed fix came back empty (byte-identical round trip).", "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
fix(react): report a `${…}` visibility predicate collapsed inside `pr…