Skip to content

Commit 301ca81

Browse files
committed
refactor: rename env toggle OS_DISABLE_REACT_PAGES -> OS_PAGE_REACT (off disables)
Page-namespaced toggle reads cleaner than an OS_DISABLE_* flag: default on, OS_PAGE_REACT=off disables. Updates cli console injection logic, spec describe, ADR-0081, showcase comment, changeset.
1 parent 5fcebf6 commit 301ca81

5 files changed

Lines changed: 17 additions & 17 deletions

File tree

.changeset/sdui-react-page-tier.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ ADR-0081: split the AI page-authoring surface into honest tiers.
1111
`'react'` is the real-React tier (executed at render by
1212
`@object-ui/react-runtime`). It runs author JS, so it is gated by a host
1313
capability that **defaults ON** (the platform trusts reviewed, draft-gated
14-
authors) and is disabled **server-side** via the `OS_DISABLE_REACT_PAGES`
15-
env var. The completeness gate now requires `source` for all three kinds.
14+
authors) and is disabled **server-side** via the `OS_PAGE_REACT=off`
15+
env toggle. The completeness gate now requires `source` for all three kinds.
1616
- `@objectstack/cli` console serving injects the disable global into the served
17-
HTML when `OS_DISABLE_REACT_PAGES` is set (read per request, no rebuild).
17+
HTML when `OS_PAGE_REACT=off` (read per request, no rebuild).
1818
- `validate-jsx-pages` lints `html`/`jsx` (constrained parse) and intentionally
1919
skips `react` (real JS, not constrained JSX).

docs/adr/0081-trusted-react-page-tier.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
1. **[rename] `kind:'jsx'``kind:'html'`.** The constrained, parse-never-execute tier (ADR-0080) is renamed to match what it is: author-written **HTML + Tailwind** (expressed as constrained JSX) compiled to the SDUI tree. `'jsx'` stays as a **deprecated alias** (already-saved pages keep loading); all authored examples/docs move to `'html'`.
1717
2. **[capability] The `html` tier now resolves the full safe native HTML tag set.** Previously only `div/span/table/code/label` + semantic sectioning tags were registered, so `<h1>/<p>/<a>/<ul>/<li>/<img>/<blockquote>…` failed as `unknown-component`. They are now registered as passthrough renderers — the tier is finally honest about "HTML".
1818
3. **[new tier] `kind:'react'` executes real React.** Its `source` is real JS/JSX (hooks, `.map`, event handlers, expressions), transpiled (Sucrase) and evaluated **in the main React tree — no sandbox** — by the new `@object-ui/react-runtime`. This is `parse = execute`: the categorical opposite of the `html` tier.
19-
4. **[security = trust, not sandbox] `react` is gated by a host capability that defaults ON.** `CAP_REACT_PAGES` is controlled only by the *host*, never by authored metadata. It defaults ON because ObjectStack pages are authored by trusted authors and pass human review (draft-gating, ADR-0033). A deployment that does NOT trust its authors disables it **server-side** — the ObjectStack runtime injects the disable global when `OS_DISABLE_REACT_PAGES` is set (one env var, no rebuild). A sandbox (iframe/worker) is the tool for *untrusted* execution; we deliberately choose **trust + review** instead, which keeps the author's code a first-class citizen of the app (shared React tree, real components, real data). This is NOT an enterprise-only feature.
19+
4. **[security = trust, not sandbox] `react` is gated by a host capability that defaults ON.** `CAP_REACT_PAGES` is controlled only by the *host*, never by authored metadata. It defaults ON because ObjectStack pages are authored by trusted authors and pass human review (draft-gating, ADR-0033). A deployment that does NOT trust its authors disables it **server-side** — the ObjectStack runtime injects the disable global when `OS_PAGE_REACT=off` (one env toggle, no rebuild). A sandbox (iframe/worker) is the tool for *untrusted* execution; we deliberately choose **trust + review** instead, which keeps the author's code a first-class citizen of the app (shared React tree, real components, real data). This is NOT an enterprise-only feature.
2020
5. **[runtime] Vendored, not depended.** `@object-ui/react-runtime` inlines the ~150-LOC react-runner core (MIT) so we fully own the injected **scope/imports surface** (the actual capability ceiling) and can **lazy-load** it — the transpiler ships in a separate chunk fetched only when a `react` page renders *with the capability on*.
2121
6. **[scope] Inject data blocks + React; leave layout to HTML.** The runtime scope exposes `React`, the curated **public data blocks** (`<ObjectTable>`, `<ObjectForm>`, charts, metrics — each a prop-driven wrapper over `SchemaRenderer`), a `<Block type=…/>` escape hatch, and the page `data`. Layout/structure is plain HTML + Tailwind (React's strength) — we do **not** bridge React children into schema-children renderers.
2222
7. **[lint] `react` is not linted by the constrained parser.** `validate-jsx-pages` (ADR-0080's build gate) validates `html`/`jsx` only; running the constrained JSX parser over real React source would false-error on hooks and expressions. The `react` tier's safety boundary is the **capability + human review**, not a static gate.
@@ -31,7 +31,7 @@
3131
| Processing | **parsed**, never executed → SDUI tree | transpiled + **executed** in the main React tree |
3232
| JavaScript | none (static literals) | full |
3333
| Safety model | safe by construction (no execution) | **trust** (capability + review); no sandbox |
34-
| Where it runs | OSS default ON | default ON; disable server-side via `OS_DISABLE_REACT_PAGES` |
34+
| Where it runs | OSS default ON | default ON; disable server-side via `OS_PAGE_REACT=off` |
3535
| Author-time gate | `os build` constrained-parse lint | none (real JS); error boundary at render |
3636
| Contract | registry `inputs` (ADR-0080) | injected scope = the ceiling |
3737

@@ -49,7 +49,7 @@ Register the safe native HTML flow/inline set (`h1–h6, p, a, ul/ol/li, dl/dt/d
4949

5050
### 3. Add `react` behind a host capability that defaults ON
5151
- **Gate**: `@object-ui/core` gains `enableCapability`/`disableCapability`/`isCapabilityEnabled` + `CAP_REACT_PAGES`. `react-pages` defaults ON. Host-only — it reads `globalThis.__OBJECTUI_CAPABILITIES_DISABLED__` (force off) and `__OBJECTUI_CAPABILITIES__` (force on); it is structurally impossible to flip from authored metadata.
52-
- **Server opt-out**: the ObjectStack console serving (framework `cli/utils/console.ts`) injects the disable global into the served HTML when `OS_DISABLE_REACT_PAGES` is set — one env var, read per request, no rebuild. This is the single server-side switch a deployment uses to turn the tier off.
52+
- **Server opt-out**: the ObjectStack console serving (framework `cli/utils/console.ts`) injects the disable global into the served HTML when `OS_PAGE_REACT=off` — one env toggle, read per request, no rebuild. This is the single server-side switch a deployment uses to turn the tier off.
5353
- **Renderer**: PageRenderer routes `kind:'react'` to a renderer that — when the capability is on — lazy-imports `@object-ui/react-runtime`, builds the scope, and renders via its error-boundaried `ReactRunner`. Capability off → a clear "React pages are disabled on this deployment" notice, not a crash.
5454

5555
### 4. Vendor the runtime; own the scope
@@ -63,4 +63,4 @@ Register the safe native HTML flow/inline set (`h1–h6, p, a, ul/ol/li, dl/dt/d
6363
- **Two safety stories, each clean.** `html` is safe by construction and stays the OSS/multi-tenant default. `react` is safe by *trust*, opt-in, and obviously so (you typed `enableCapability`).
6464
- **No build/deploy for either.** `html` compiles at save; `react` transpiles at render (lazy chunk). Neither needs a bundler in the author loop.
6565
- **AI fit.** AI writes both fluently. For `react`, correctness is bounded by the injected scope + human review (ADR-0033), not by hoping the model never writes a bad effect.
66-
- **Cost.** `react` runs author code in the main tree: a thrown render is caught by the error boundary, but an author can still write a slow/incorrect page. That is the accepted price of the trusted tier; a deployment that does not trust its authors flips `OS_DISABLE_REACT_PAGES`. Revisit if a future need arises for *untrusted* rich pages — that would be a *different* ADR (sandboxed execution), not this one.
66+
- **Cost.** `react` runs author code in the main tree: a thrown render is caught by the error boundary, but an author can still write a slow/incorrect page. That is the accepted price of the trusted tier; a deployment that does not trust its authors sets `OS_PAGE_REACT=off`. Revisit if a future need arises for *untrusted* rich pages — that would be a *different* ADR (sandboxed execution), not this one.

examples/app-showcase/src/pages/crm-workbench.page.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { definePage } from '@objectstack/spec/ui';
2424
* NOTE: `kind:'react'` executes author code, so it is gated by the host
2525
* capability `CAP_REACT_PAGES`, which defaults ON (the platform trusts its
2626
* reviewed, draft-gated authors). A deployment that does not trust its authors
27-
* turns it off server-side by setting `OS_DISABLE_REACT_PAGES`, in which case
27+
* turns it off server-side with `OS_PAGE_REACT=off`, in which case
2828
* this page renders a "disabled on this deployment" notice instead of executing.
2929
*/
3030
export const CrmWorkbenchPage = definePage({

packages/cli/src/utils/console.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -292,15 +292,15 @@ export function createConsoleStaticPlugin(distPath: string, options?: { isDev?:
292292
return;
293293
}
294294

295-
// A deployment that does not trust its page authors disables the
296-
// `kind:'react'` tier (which executes author JS in the main React tree)
297-
// by setting `OS_DISABLE_REACT_PAGES`. We inject the disable global the
298-
// console's capability gate reads; the flag is ON by default otherwise.
299-
// Read per request (env can change without a rebuild — index.html is
300-
// re-read on every fallback hit too).
295+
// The `kind:'react'` page tier (executes author JS in the main React
296+
// tree) is ON by default. A deployment that does not trust its page
297+
// authors turns it off with `OS_PAGE_REACT=off`; we then inject the
298+
// disable global the console's capability gate reads. Read per request
299+
// (env can change without a rebuild — index.html is re-read on every
300+
// fallback hit too).
301301
const reactPagesDisabled = (): boolean => {
302-
const v = String(process.env.OS_DISABLE_REACT_PAGES ?? '').trim().toLowerCase();
303-
return v === '1' || v === 'true' || v === 'yes' || v === 'on';
302+
const v = String(process.env.OS_PAGE_REACT ?? '').trim().toLowerCase();
303+
return v === 'off' || v === '0' || v === 'false' || v === 'no' || v === 'disabled';
304304
};
305305

306306
const readIndexHtml = () => {

packages/spec/src/ui/page.zod.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ export const PageSchema = lazySchema(() => z.object({
365365
"executed) to the tree (ADR-0080; the legacy value 'jsx' is a deprecated alias); " +
366366
"react = real-React source executed at render by the runtime (ADR-0081); it " +
367367
"runs author JS, so it is gated by a host capability that defaults ON and is " +
368-
"disabled server-side via OS_DISABLE_REACT_PAGES.",
368+
"disabled server-side via the OS_PAGE_REACT=off env toggle.",
369369
),
370370

371371
/**

0 commit comments

Comments
 (0)