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 }}
Repository navigation
Commit 1fab654
Browse filesBrowse the repository at this point in the historyBrowse files
fix(react): stop advertising app as a bound expression-scope root
objectui#8164 removed `app` from `buildExpressionScope` (the objectui#8155
option-B ruling) on three faces inside app-shell, but never asked which OTHER
surfaces state the same fact. The post-merge tier audit found the residue.
The blocking one: `@object-ui/react`'s `SCOPE_TIER_ADVICE['app-shell']` — the
paragraph printed in production when a predicate cannot be evaluated — still
read "plus `app` and `features`". It is the line an author sees at exactly the
moment a stale `app.*` predicate faults, so it answered "why did this not
resolve?" by naming the root that is the reason, and its own byte-pin
(`expect(msg).toContain('`app`')`) held the false sentence in place.
Swept as a class, not as those two coordinates: every place in the tree that
stated `app` was a bound expression-scope root — diagnostic copy, ambient-scope
docblocks in react / core / components / plugin-detail / plugin-form / app-shell
/ console, a README, and fourteen test fixtures that transcribed the old bag —
is corrected. The two app-shell fixtures now call `buildExpressionScope` instead
of transcribing it, so that pair cannot drift again.
Also corrects two false sentences in the release-bound changeset:
objectstack#16420 was closed `not_planned` 2026-09-07 (not left open), and the
consequence of a stale `app.*` predicate is not uniformly "fails open" — it is
per surface, and the seven directions are now measured and listed.
Nothing unrelated to `app` moved: no export line added or removed anywhere in
the diff, no barrel or package manifest touched, and the `app` prop and React
context field on `ExpressionProvider` (never CEL roots) are untouched.
Part of #8155
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH
Copy file name to clipboardExpand all lines: .changeset/7727-conditional-formatting-record-scope.md
+28-7Lines changed: 28 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -60,13 +60,34 @@ consumer aligns to it, rather than the engine growing a root to match this consu
60
60
population cannot be measured from this repository.** In-tree usage is zero — swept
61
61
across `packages/`, `apps/`, `examples/` and `content/` with a firing control — but
62
62
metadata authored in real deployments lives outside this tree and no sweep here can
63
-
see it. Any predicate that reads `app.*` — a conditional-formatting condition, an
64
-
action `visible` / `disabled`, a field `visibleWhen` — stops resolving and, because
65
-
unresolvable visibility predicates **fail open**, will start reading as "yes" rather
66
-
than erroring. That is the accepted cost of the ruling, not an oversight. There is no
67
-
replacement root: `app` was never in the protocol. If you need a "current app" value in
68
-
a predicate, that is a spec/engine vocabulary widening to be filed (the producer-side
69
-
card, objectstack#16420, stays open as the record to reopen).
63
+
see it. Any predicate that reads `app.*` stops resolving. There is no replacement
64
+
root: `app` was never in the protocol. If you need a "current app" value in a
65
+
predicate, that is a spec/engine vocabulary widening to be filed fresh — the
66
+
producer-side card objectstack#16420 was closed `not_planned` by the same ruling
67
+
(2026-09-07), so there is no open record waiting for it.
68
+
69
+
**What a stale `app.*` predicate does now depends on the surface — the direction is
70
+
NOT uniform, and two of them fail the safe way.** Measured per surface on the merged
71
+
head, each with a resolvable control predicate firing in the same run:
72
+
73
+
| Surface | Entry point | A stale `app.*` predicate now |
74
+
|---|---|---|
75
+
| Conditional-formatting `condition`|`resolveConditionalFormatting` → `evalRowPredicate` (`fallback: false`) |**fails CLOSED** — the rule silently stops matching, no style is applied |
76
+
| Row/header action `visible` / `disabled`|`evalRowPredicate` (`fallback: false`) |**fails CLOSED** — the action is hidden / left enabled |
77
+
| Action `visible` on `action-button` / `action-menu` / `action-bar`|`useCondition(…, { throwOnError: true })`|**fails CLOSED** — hidden, with a one-time console warning |
78
+
| Action `visible` on `action-icon` / `action-group`|`useCondition` (default) |**fails OPEN** — the action is shown |
79
+
| Field `visibleWhen` (form field rules) |`resolveFieldRuleState` → `evalFieldPredicate` (fallback `true`) |**fails OPEN** — the field is shown |
80
+
| Field `visibleWhen` (app-shell object field) and nav / area `visible`|`isObjectFieldVisible` / `evaluateVisibility`|**fails OPEN** — shown, with a console diagnostic |
81
+
| Field `readonlyWhen` / `requiredWhen`|`resolveFieldRuleState` (fallback `false`) |**fails CLOSED** — not readonly, not required |
82
+
83
+
So the cost is not one shape: on the fail-OPEN surfaces a gate that used to hide
84
+
something starts showing it, and on the fail-CLOSED surfaces a rule that used to fire
85
+
silently stops. Both are accepted costs of the ruling, not oversights — but they need
86
+
opposite checks after upgrading, which is why they are listed apart rather than
87
+
summarised. Every faulting predicate warns on the console; the app-shell diagnostic
88
+
names the roots this tier really binds, and objectui#8155's follow-up removed `app`
89
+
from that list so it no longer sends an author back to the root that is the reason
0 commit comments