Skip to content

Commit c937747

Browse files
committed
feat(cli): react pages default off; docs, ADR-0081 amendment, showcase opt-in
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X4d6XVfcfxL17bMpptytcc
1 parent c29ad45 commit c937747

9 files changed

Lines changed: 71 additions & 30 deletions

File tree

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
'@objectstack/cli': patch
3+
'@objectstack/spec': patch
4+
---
5+
6+
feat: `kind:'react'` pages are off unless the deployment turns them on
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
**BREAKING**: the `kind:'react'` page tier, which runs page source in the Console, now defaults to **off** (ADR-0081, amended). A deployment that serves react pages turns the tier on with `OS_PAGE_REACT_ENABLED=true`, in the environment or the project's `.env` file. With it off, each react page shows a notice naming that switch instead of running, and the Console document's content-security-policy no longer allows `'unsafe-eval'`. `kind:'html'` pages are unaffected.
12+
13+
### Migration: FROM → TO
14+
15+
```bash
16+
# FROM — react pages ran unless the deployment set OS_PAGE_REACT=off
17+
# TO — they run only when the deployment opts in
18+
OS_PAGE_REACT_ENABLED=true
19+
```
20+
21+
The earlier name is still read while the new one is unset: `OS_PAGE_REACT=on` turns the tier on and `OS_PAGE_REACT=off` keeps it off, each with a one-time deprecation warning. `true`, `1`, `on`, `yes` and `enabled` turn it on; unset, `false`, `0`, `off`, `no` and `disabled` leave it off; any other value leaves it off and is reported once in the server log.
22+
23+
The server now writes the Console's capability global in both directions, so a Console build with either default follows the server's setting. `examples/app-showcase` opts in through its `.env`, so its three react pages keep running. The `PageSchema.kind` description in `@objectstack/spec` now states the new default.
24+
25+
Operators who collect content-security-policy reports may see one `script-src` eval refusal per Console load while the tier is off: the Console's schema library probes for `eval` once at boot and falls back without it. Nothing on the page is affected, and the probe is removed on the objectui side.

‎content/docs/protocol/objectui/layout-dsl.mdx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ write the page body as a string. Pick by what the page needs:
3939
For `html` / `react`, the page body is the `source` string (it is the
4040
source-of-truth; `regions` is ignored). `html` is **safe by construction** (it
4141
is parsed into the same SDUI tree this document describes, never executed).
42-
`react` executes author code, so it is gated by a host capability that is **ON
43-
by default** and disabled per-deployment with the `OS_PAGE_REACT=off` env
44-
variable. Both tiers can embed the platform's real data components
42+
`react` executes author code, so it is gated by a host capability that is **OFF
43+
by default** and turned on per deployment with the `OS_PAGE_REACT_ENABLED=true`
44+
env variable. Both tiers can embed the platform's real data components
4545
(`<ObjectTable>`, `<ObjectForm>`, charts, metrics).
4646

4747
> **Learn more:** [ADR-0080](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0080-ai-authored-ui-jsx-source.md)

‎content/docs/ui/pages.mdx‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,9 @@ is rejected by the schema rather than rendering empty.
108108
`'jsx'` keep loading as `html` pages.
109109

110110
Because `kind: 'react'` executes author JavaScript, it is gated by a host capability that
111-
defaults **ON** and is disabled server-side per deployment with `OS_PAGE_REACT=off`. The
112-
`html` tier is unaffected by that switch — it is never executed.
111+
defaults **OFF** and is turned on server-side per deployment with
112+
`OS_PAGE_REACT_ENABLED=true`. The `html` tier is unaffected by that switch — it is never
113+
executed.
113114

114115
<Callout type="warn">
115116
Neither source tier is styled with Tailwind utility classes. A page's `source` is

‎content/docs/ui/react-pages.mdx‎

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,20 +58,27 @@ The two tiers also differ in what they give you for layout, in opposite directio
5858

5959
## The security gate
6060

61-
Because the react tier runs author JavaScript, it is gated by a host capability named
62-
`react-pages`, which defaults **ON** — the platform's assumption is that page authors
63-
are reviewed and draft-gated.
61+
Because the react tier runs author JavaScript in every viewer's session, with no sandbox,
62+
it is gated by a host capability named `react-pages`, which defaults **OFF**. The tier is
63+
for deployments whose page authors are trusted and reviewed.
6464

65-
A deployment that does not trust its page authors turns the tier off **server-side**:
65+
A deployment that trusts its page authors turns the tier on **server-side**:
6666

6767
```bash
68-
OS_PAGE_REACT=off objectstack start
68+
OS_PAGE_REACT_ENABLED=true objectstack start
6969
```
7070

71-
The server then injects the console's capability-disable flag, and every `kind:'react'`
72-
page renders an explanatory notice instead of executing. `off`, `0`, `false`, `no` and
73-
`disabled` are all accepted spellings. `kind:'html'` pages are unaffected — they were
74-
never executed in the first place, which is the whole point of the split.
71+
or with the same line in the project's `.env` file. `true`, `1`, `on`, `yes` and `enabled`
72+
turn it on; unset, `false`, `0`, `off`, `no` and `disabled` leave it off. Any other value
73+
leaves it off and is reported once in the server log. The earlier name `OS_PAGE_REACT`
74+
(`on` / `off`) is still read when `OS_PAGE_REACT_ENABLED` is unset, with a deprecation
75+
warning.
76+
77+
With the tier off, every `kind:'react'` page renders a notice that names the switch
78+
instead of executing, and the Console's content-security-policy refuses `eval`. With it
79+
on, the server tells the Console to run react pages and the policy allows the `Function`
80+
call the runtime compiles them with. `kind:'html'` pages are unaffected either way — they
81+
are never executed, which is the whole point of the split.
7582

7683
## What is in scope
7784

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ADR-0081: A trusted `kind:'react'` page tier — real React executed in the main tree, gated by a host capability; and renaming `kind:'jsx'` → `kind:'html'`
22

3-
**Status**: Accepted (2026-06-30) — framework scope implemented (`html`/`react` page kinds + `jsx` alias, lint split `validate-jsx-pages`/`validate-react-pages`, `OS_PAGE_REACT=off` server toggle); the `@object-ui/react-runtime` executor + `CAP_REACT_PAGES` gate live in objectui.
3+
**Status**: Accepted (2026-06-30) — framework scope implemented (`html`/`react` page kinds + `jsx` alias, lint split `validate-jsx-pages`/`validate-react-pages`, `OS_PAGE_REACT=off` server toggle); the `@object-ui/react-runtime` executor + `CAP_REACT_PAGES` gate live in objectui. **Amended** (2026-10-25 — the `react` tier defaults **OFF**; a deployment opts in with `OS_PAGE_REACT_ENABLED=true`. See "Amendment (2026-10-25): default off, opt in per deployment".)
44
**Deciders**: ObjectStack Protocol Architects
55
**Builds on**: [ADR-0080](./0080-ai-authored-ui-jsx-source.md) (AI authors a *constrained* JSX text, parsed-never-executed, into the SDUI tree; the component registry is the contract; capability ≠ contract), [ADR-0033](./0033-ai-assisted-metadata-authoring.md) (AI writes metadata via draft-gated `saveMetaItem` — the human-review boundary), [ADR-0077](./0077-authoring-surface-boundary-hook-flow-validation.md) (route by intent; verifiability tier = friction tier; **code is the flagged escape hatch**), [ADR-0078](./0078-no-silently-inert-metadata.md) (no silently-inert metadata).
66
**Consumers**: `@objectstack/spec` (`packages/spec/src/ui/page.zod.ts` — `kind` gains `'html'` and `'react'`; `'jsx'` kept as a deprecated alias), `@objectstack/lint` (`validate-jsx-pages` — lints `html`/`jsx`, **skips** `react`), `../objectui` `@object-ui/react-runtime` (**new** — the trusted runtime-React executor) + `@object-ui/core` (the host capability gate) + `@object-ui/components` (PageRenderer routing + the full HTML tag set).
@@ -13,6 +13,8 @@
1313

1414
> **Amendment (2026-06-30 — ADR-0065 styling correction).** The "HTML + **Tailwind**" framing for page `source` is **superseded on styling**. A page's `source` is *runtime metadata*, so the console's build-time Tailwind never scans it — authored utility `className`s silently produce no CSS (the exact failure ADR-0065 was written to prevent; the Task Desk modal's `bg-black/50` backdrop rendered transparent). The tiers themselves stand; only the styling **primitive** changes: `kind:'html'` styles via the registered components' structured props + a JSON `style` object; `kind:'react'` styles via inline `style={{}}` with `hsl(var(--token))` theme colors and renders drawer/modal overlays through `<ObjectForm formType="drawer"|"modal">` (never a hand-rolled `fixed inset-0`). **Do not author Tailwind classes in page source.** See [ADR-0065](./0065-sdui-styling-model.md).
1515
16+
> **Amendment (2026-10-25): default off, opt in per deployment.** Decision 3 and TL;DR 4 are **superseded on the default**; the tier, its gate and its host-only control stand. `CAP_REACT_PAGES` now defaults **OFF** in `@object-ui/core`, and the ObjectStack console serving (`cli/utils/console.ts`, `readReactPagesTier`) turns it on only when the operator sets `OS_PAGE_REACT_ENABLED=true` (`OS_PAGE_REACT=on`, the earlier name, is still read through `readEnvWithDeprecation`). The server writes the capability global in **both** directions — the enable list when on, the disable list when off — so its answer holds against a Console build with either default. The Console document's content-security-policy keeps `'unsafe-eval'` tied to the same per-request read. Off, a react page renders the named "React pages are disabled on this deployment" notice. **Why the default moved:** "trust + review" names a property of one deployment's page authors, which only that deployment's operator can vouch for, so the platform default no longer vouches for it on every deployment; the operator makes the trust decision explicitly. Reference deployments that ship react pages opt in in their own configuration (`examples/app-showcase/.env`).
17+
1618
## TL;DR
1719

1820
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'`.

0 commit comments

Comments
 (0)