Skip to content

Commit 035d3fa

Browse files
os-teslaclaude
andauthored
docs: publish each page's own values through the scope channel on the three remaining teaching surfaces (#9376)
* docs: publish the page's own values through the scope channel, and name the three props the renderer never reads The three remaining teaching surfaces from the objectui#8021 sweep — the root README, the expression guide and the architecture guide — each wrote the page's values onto the `SchemaRenderer` element and read them back under a bare head name. `SchemaRendererProps` declares exactly one prop, `schema`; `data`, `dataSource` and `debug` are forwarded to the component the schema names, so nothing throws, nothing warns, and the expression reaches the screen as the characters the author typed. Both coordinates move together on every site, per objectui#8021 leg D: * the six forwarded look-alikes become the provider that actually carries the value — `PredicateScopeProvider` for the expression scope, `SchemaRendererProvider` for the adapter and for `debug`; * the head names are judged one at a time against the scope the tree builds. Thirty-two stay bare, because after the objectui#9308 ruling the root set is open: every key the host publishes is a root. Five move to `record.*` — the two form-row sites and the three predicates written against a `form` root that nothing in ObjectUI publishes. `content/docs/guide/architecture.md` also carried the one `data.*` gate on these pages. The census could not see it — its classifier held `data` in the ACCEPTED root set — and it is exactly the spelling the ruling retires, so it moves to `record.age` and the guide states the verdict flip: a missing root and a present-but-undefined root are not the same, so a `data.*` gate that used to hide its node on every row now shows it. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ * docs(expressions): state what an unresolvable expression actually does, measured The Expression Errors block claimed `${user.invalidProperty}` yields `Cannot read property 'invalidProperty' of undefined`. objectui#9297's addendum flagged that as looking wrong and explicitly left it NOT MEASURED. Measured now, on the built evaluator, and it is wrong in both directions: { user: { name: 'Alice' } } ${user.invalidProperty} => undefined {} (no user root) ${user.invalidProperty} => "${user.invalidProperty}" control, same instrument: ${user.name} => "Alice" Nothing is thrown on either path. A missing MEMBER of a published root renders as nothing; a missing ROOT renders as the characters the author typed, with one console line. Repaired here rather than filed because the same commit series now states, two sections up, that an unresolvable template is returned as its own source text — leaving the old sentence would have made the page contradict itself. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ * docs: the missing-root path prints one console line, so say so Two sentences this branch added taught silence on a path that is not silent. Measured just now on the built evaluator (`packages/core/dist`, console captured, one instance per leg): {} (no user root) ${user.name} => "${user.name}" 1 console.warn { user: { name: 'Alice' } } ${user.invalidProperty} => undefined 0 lines { user: { name: 'Alice' } } ${user.name} => "Alice" 0 lines harness control console.warn(sentinel) => fires 1 line The line is `Failed to evaluate expression: ...` from `reportEvaluationFault` in `packages/core/src/evaluator/ExpressionEvaluator.ts`, which falls back to `console.warn` whenever the caller supplies no `onFault`. There is no dev-only gate on it, and the six `evaluator.evaluate(...)` call sites in `packages/react/src/SchemaRenderer.tsx` that resolve `content`, `properties.*` and `props.*` all pass no options, so the doc-relevant path is exactly the built-in warn. The one-per-authored-source ceiling in that same function is why the count is one line and not one per render. This branch already stated the true half two sections down in the expression guide's Expression Errors table — "the template's own source text, and one line on the console" — so the page contradicted itself inside one diff. Both sentences now carry that same phrasing rather than new wording. `content/docs/guide/schema-rendering.md` carries the same claim at the same head; it is outside this pull request's file set and is left byte-identical. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 6748587 commit 035d3fa

3 files changed

Lines changed: 159 additions & 50 deletions

File tree

‎README.md‎

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ npm install @object-ui/react @object-ui/components
7272

7373
```tsx
7474
import React from 'react'
75-
import { SchemaRenderer } from '@object-ui/react'
75+
import { PredicateScopeProvider, SchemaRenderer } from '@object-ui/react'
7676
// Importing the package registers every default renderer as a side effect —
7777
// there is no separate registration call.
7878
import '@object-ui/components'
@@ -92,16 +92,28 @@ const schema = {
9292
}
9393

9494
function App() {
95-
const data = {
95+
// Every key of this object becomes a root the schema's expressions can read —
96+
// `stats` here is what answers `${stats.users}`.
97+
const scope = {
9698
stats: { users: 1234, revenue: "$56,789", orders: 432 }
9799
}
98100

99-
return <SchemaRenderer schema={schema} data={data} />
101+
return (
102+
<PredicateScopeProvider scope={scope}>
103+
<SchemaRenderer schema={schema} />
104+
</PredicateScopeProvider>
105+
)
100106
}
101107

102108
export default App
103109
```
104110

111+
Expression scope reaches the renderer through the provider, never through a prop on the
112+
element. `SchemaRenderer` declares exactly one prop, `schema`, and forwards every other prop
113+
it is handed to the component the schema names — so a value passed as `data={…}` is neither
114+
read nor refused, and the expression that wanted it is returned as its own source text, with
115+
nothing thrown and one line on the console.
116+
105117
### Bring your own backend
106118

107119
Use the shell and views without the full console infrastructure — your routing, your auth, your API:
@@ -171,6 +183,10 @@ docs render, a smoke test mounts, and AI agents use as a few-shot corpus.
171183

172184
## Copy-Paste Schemas
173185

186+
A `${name.…}` in any of these reads the root `name` off the scope the host published — see
187+
["Basic Usage"](#basic-usage) for the provider that publishes one. A head name nothing
188+
published is not an error: the expression is returned as its own source text.
189+
174190
#### 📝 Contact Form
175191

176192
```json
@@ -258,9 +274,9 @@ Object UI talks to any backend through one `DataSource` interface.
258274
npm install @object-ui/data-objectstack
259275
```
260276

261-
```typescript
277+
```tsx
262278
import { createObjectStackAdapter } from '@object-ui/data-objectstack';
263-
import { SchemaRenderer } from '@object-ui/react';
279+
import { SchemaRenderer, SchemaRendererProvider } from '@object-ui/react';
264280
import type { BaseSchema } from '@object-ui/types';
265281

266282
// Your page schema — "Render a schema" above writes one out in full.
@@ -271,10 +287,18 @@ const dataSource = createObjectStackAdapter({
271287
token: 'your-auth-token'
272288
});
273289

274-
// Use with any component
275-
<SchemaRenderer schema={schema} dataSource={dataSource} />
290+
// The adapter is injected through the provider — `SchemaRenderer` does not read
291+
// a `dataSource` prop, it forwards it to the component the schema names.
292+
<SchemaRendererProvider dataSource={dataSource}>
293+
<SchemaRenderer schema={schema} />
294+
</SchemaRendererProvider>
276295
```
277296

297+
⛔ `dataSource` is the **adapter** — the object data renderers call `find()` on — and not an
298+
expression root. It is a different channel from the expression scope above: publish the values
299+
your `${…}` expressions read with `PredicateScopeProvider`, and inject the adapter your data
300+
components query with `SchemaRendererProvider`.
301+
278302
### Custom Data Sources
279303

280304
Adapt any backend (REST, GraphQL, Firebase, …) by implementing `DataSource`:

‎content/docs/guide/architecture.md‎

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ ObjectUI is organized as a PNPM monorepo with clear separation of concerns:
5252
- **Contains**: Schema validation, expression evaluation, registries
5353
- **Constraint**: No UI library dependencies, logic only
5454
- **Features**:
55-
- Expression engine (`visible: "${data.age > 18}"`)
55+
- Expression engine (`visible: "${record.age > 18}"`)
5656
- Schema registry and validation
5757
- Event system
5858

@@ -132,14 +132,19 @@ A backend system sends a JSON schema:
132132

133133
The `SchemaRenderer` component:
134134

135-
1. Receives the schema + data context
135+
1. Receives the schema, and reads the expression scope off the context above it
136136
2. Evaluates expressions (`${user.name}`)
137137
3. Looks up the component type in the registry
138138
4. Recursively renders child schemas
139139
5. Handles events and state updates
140140

141+
The scope does **not** arrive as a prop. `SchemaRenderer` declares exactly one prop, `schema`,
142+
and forwards everything else it is handed to the component the schema names — so a `data={…}`
143+
written on the element is neither read nor refused. The host publishes its values with
144+
`PredicateScopeProvider`, and every key it publishes becomes a root the expressions can read:
145+
141146
```tsx
142-
import { SchemaRenderer } from '@object-ui/react'
147+
import { PredicateScopeProvider, SchemaRenderer } from '@object-ui/react'
143148
import type { BaseSchema } from '@object-ui/types'
144149

145150
// The schema from step 1, as the object the renderer receives.
@@ -153,12 +158,25 @@ const schema: BaseSchema = {
153158
}
154159

155160
function App() {
156-
const data = { user: { name: 'Alice' } }
161+
const scope = { user: { name: 'Alice' } }
157162

158-
return <SchemaRenderer schema={schema} data={data} />
163+
return (
164+
<PredicateScopeProvider scope={scope}>
165+
<SchemaRenderer schema={schema} />
166+
</PredicateScopeProvider>
167+
)
159168
}
160169
```
161170

171+
An app built on `@object-ui/app-shell` does not mount this provider itself: the shell's
172+
`ExpressionProvider` already feeds the same channel with the signed-in `user` (also readable as
173+
`current_user`) and `features`. On top of what the host published, the renderer supplies
174+
`record` — the row a record surface is bound to — and `page`, the page-local variables.
175+
176+
⛔ `SchemaRendererProvider`'s `dataSource` is **not** an expression root. It carries the host's
177+
`DataSource` *adapter*, the object data renderers call `find()` on; the two are different
178+
channels on purpose.
179+
162180
### 3. Component Registry Lookup
163181

164182
The registry maps type strings to React components:
@@ -244,11 +262,17 @@ ObjectUI includes a powerful expression engine for dynamic UIs:
244262
{
245263
"type": "button",
246264
"label": "Submit",
247-
"visible": "${form.isValid && !form.isSubmitting}",
248-
"disabled": "${form.isSubmitting}"
265+
"visible": "${current_user.role === 'admin'}",
266+
"disabled": "${record.status === 'locked'}"
249267
}
250268
```
251269

270+
`current_user` is the signed-in user the host's `ExpressionProvider` publishes; `record` is the
271+
row a record surface is bound to, and is the only spelling a row field has — the bare shorthand
272+
(`status`) and the wrong-layer `data.status` were both retired on runtime record surfaces
273+
(objectui#5330 phase 2). A head name outside the scope is not refused: the predicate is
274+
unevaluable, this surface fails soft, and the node is shown on every row.
275+
252276
A button's text key is `label`, and `text` is not a `ButtonSchema` key at all. Nothing
253277
refuses the misspelling either: `BaseSchema` is `.passthrough()`, so the validator KEEPS
254278
the unknown key, and the renderer — which reads `schema.label` — never looks at it.

0 commit comments

Comments
 (0)