Skip to content

Commit d23d6eb

Browse files
os-zhuangclaude
andauthored
feat(page): kind:'html' (full HTML tags) + trusted kind:'react' tier (#2105)
* feat(react-runtime): trusted runtime-React for kind:'react' (vendored react-runner) New @object-ui/react-runtime: Sucrase transpile + scope-eval (no sandbox), renders real JSX/TSX (any HTML + JS + hooks/useState/map/onClick) in the main React tree with injected scope (React, registered components, page data) + built-in error boundary. The *trusted* tier — gated to enterprise/private deploys, lazy-loadable. Browser-verified: real JSX page renders + clicks mutate state, via the package. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(page): kind:'html' (full HTML tags) + trusted kind:'react' tier - PageRenderer routes kind:'react' (capability-gated, lazy-loads @object-ui/react-runtime) and renders kind:'html', the former kind:'jsx' (kept as a deprecated alias). - core: runtime capability gate (enableCapability/isCapabilityEnabled, CAP_REACT_PAGES) — default-closed, host-only, never authored metadata. - components: register the full safe native HTML tag set (h1-h6, p, a, ul/ol/li, img, blockquote, pre, strong/em, ...) so the html tier lives up to its name; react-page renderer injects React + public data blocks + page data into the trusted runtime. - dev: sdui-tiers-preview exercises both tiers through the real PageRenderer. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(console): drop unused React import in react-page-preview PoC (tsc noUnusedLocals) * feat(react-page): inject live dataSource so kind:'react' can compose ObjectForm/ListView The trusted react tier now wires the real data components for complex business UIs (master/detail, etc.): ReactKindPage reads useAdapter() and (a) injects it as `dataSource` into the public data-block wrappers (list-view reads it from props) and (b) wraps the page in a SchemaRendererProvider (object-form reads it from context). Also injects `useAdapter` into the page scope for live queries. dev: sdui-workbench-preview mounts the real ListView + ObjectForm against an in-memory adapter — browser-verified master/detail (row click loads the record into the form; save creates/updates and refreshes the list + KPIs). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(core): kind:'react' capability defaults ON, disable via OS_DISABLE_REACT_PAGES Per platform-author decision: react-pages is no longer enterprise-gated/default- off. The platform trusts its reviewed, draft-gated page authors, so the gate defaults ON. A deployment that does not trust its authors disables it server- side — the runtime honors globalThis.__OBJECTUI_CAPABILITIES_DISABLED__, which the ObjectStack console serving injects when OS_DISABLE_REACT_PAGES is set. The disabled-state notice is reframed accordingly (no longer 'enterprise only'). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor: rename env toggle OS_DISABLE_REACT_PAGES -> OS_PAGE_REACT (off disables) Cleaner page-namespaced toggle (default on; OS_PAGE_REACT=off disables) instead of an OS_DISABLE_* flag. * chore(dev): workbench preview proves default-on + simulates OS_PAGE_REACT=off Drops the explicit enableCapability (react-pages is ON by default) and adds a ?disable=1 switch that sets __OBJECTUI_CAPABILITIES_DISABLED__ to render the disabled notice — both Chrome-verified. --------- Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 4930bfb commit d23d6eb

24 files changed

Lines changed: 849 additions & 6 deletions

.changeset/config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"@object-ui/core",
99
"@object-ui/i18n",
1010
"@object-ui/react",
11-
"@object-ui/components", "@object-ui/sdui-parser",
11+
"@object-ui/components", "@object-ui/react-runtime", "@object-ui/sdui-parser",
1212
"@object-ui/fields",
1313
"@object-ui/layout",
1414
"@object-ui/data-objectstack",

.changeset/sdui-react-runtime.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
"@object-ui/react-runtime": minor
3+
"@object-ui/components": minor
4+
"@object-ui/core": minor
5+
"@object-ui/console": patch
6+
---
7+
8+
Three-tier AI page authoring: `kind:'html'` and a trusted `kind:'react'` tier.
9+
10+
- **`@object-ui/react-runtime`** (new) — the trusted runtime-React tier for
11+
`kind:'react'` pages (vendored react-runner: Sucrase transpile + scope-eval,
12+
no sandbox). Renders real JSX/TSX (any HTML + JS + hooks/useState/map/onClick)
13+
in the main React tree with an injected scope (React, the public data blocks,
14+
page data) and a built-in error boundary.
15+
- **`@object-ui/core`** — new runtime capability gate (`enableCapability` /
16+
`disableCapability` / `isCapabilityEnabled`, `CAP_REACT_PAGES`). `react-pages`
17+
defaults **ON** (the platform trusts reviewed, draft-gated authors); a
18+
deployment turns it OFF server-side (the runtime injects the disable global
19+
when `OS_DISABLE_REACT_PAGES` is set). Never controlled from authored metadata.
20+
- **`@object-ui/components`** — PageRenderer now routes `kind:'react'`
21+
(capability-gated, lazy-loads the runtime) and renders `kind:'html'` (the
22+
former `kind:'jsx'`, still accepted as a deprecated alias). The `html` tier
23+
now resolves the full safe native HTML tag set (h1–h6, p, a, ul/ol/li, img,
24+
blockquote, pre, strong/em, …) so authored HTML lives up to its name.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<!doctype html><html><head><meta charset="UTF-8"/><title>kind:react preview</title></head>
2+
<body><div id="root"></div><script type="module" src="/dev/react-page-preview.tsx"></script></body></html>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/* ADR: kind:'react' PoC — inlined react-runner (transpile + scope-eval, no sandbox).
2+
* Renders REAL JSX with useState/map/onClick + an injected component + data. */
3+
import '../src/index.css';
4+
import { createRoot } from 'react-dom/client';
5+
import { ReactRunner } from '@object-ui/react-runtime';
6+
7+
/* ---- a REAL kind:'react' page source (full JS) ---- */
8+
const source = `
9+
function Page() {
10+
const [count, setCount] = React.useState(0);
11+
const total = data.reduce((s, r) => s + r.amount, 0);
12+
const top = [...data].sort((a, b) => b.amount - a.amount)[0];
13+
return (
14+
<section className="min-h-screen space-y-5 bg-slate-50 p-10">
15+
<h1 className="text-3xl font-bold text-slate-900">Live React Page (kind:'react')</h1>
16+
<p className="text-slate-500">Real JSX — useState, map, onClick, arbitrary JS — running in the main React tree.</p>
17+
<button
18+
className="rounded-lg bg-indigo-600 px-5 py-2.5 font-semibold text-white shadow hover:bg-indigo-700"
19+
onClick={() => setCount((c) => c + 1)}
20+
>
21+
Clicked {count} times
22+
</button>
23+
<div className="grid grid-cols-3 gap-4">
24+
{data.map((r, i) => (
25+
<div key={i} className="rounded-xl border bg-white p-5 shadow-sm">
26+
<div className="text-sm text-slate-500">{r.name}</div>
27+
<div className="mt-1 text-2xl font-bold">{r.amount.toLocaleString()}</div>
28+
</div>
29+
))}
30+
</div>
31+
<div className="text-lg font-medium">Total {total.toLocaleString()} · top {top.name}</div>
32+
<ObjectGrid object="account" rows={data.length} />
33+
</section>
34+
);
35+
}
36+
`;
37+
const scope = {
38+
data: [{ name: 'Acme', amount: 100 }, { name: 'Globex', amount: 250 }, { name: 'Initech', amount: 75 }],
39+
ObjectGrid: (p: any) => (
40+
<table className="mt-2 w-full border text-left text-sm" data-object={p.object}>
41+
<tbody><tr className="border-b bg-slate-100"><td className="p-2 font-semibold">object-grid({p.object}) — {p.rows} rows (injected component)</td></tr></tbody>
42+
</table>
43+
),
44+
};
45+
createRoot(document.getElementById('root')!).render(<ReactRunner code={source} scope={scope} />);

apps/console/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@object-ui/console",
33
"version": "11.2.0",
4-
"description": "ObjectStack Console opinionated, fork-ready runtime console built on @object-ui/app-shell with the full plugin set wired up. Ships as a Hono UI plugin serving a pre-built SPA.",
4+
"description": "ObjectStack Console \u2014 opinionated, fork-ready runtime console built on @object-ui/app-shell with the full plugin set wired up. Ships as a Hono UI plugin serving a pre-built SPA.",
55
"license": "MIT",
66
"type": "module",
77
"homepage": "https://www.objectui.org/docs/guide/console",
@@ -113,6 +113,8 @@
113113
"vitest": "^4.1.9"
114114
},
115115
"dependencies": {
116-
"@object-ui/sdui-parser": "workspace:*"
116+
"@object-ui/sdui-parser": "workspace:*",
117+
"@object-ui/react-runtime": "workspace:*",
118+
"sucrase": "^3.35.0"
117119
}
118120
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>SDUI tiers preview — kind:'html' + kind:'react'</title>
7+
</head>
8+
<body>
9+
<div id="root"></div>
10+
<script type="module" src="/src/sdui-tiers-preview.tsx"></script>
11+
</body>
12+
</html>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" />
4+
<title>kind:'react' master-detail (real ListView + ObjectForm)</title></head>
5+
<body><div id="root"></div><script type="module" src="/src/sdui-workbench-preview.tsx"></script></body>
6+
</html>
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/* Browser preview for the two AI-authoring tiers, each rendered through the
2+
* REAL PageRenderer (not a private harness):
3+
* - kind:'html' — constrained JSX with the full native HTML tag set (h1/p/
4+
* a/ul/li/img/strong/blockquote), parsed, never executed.
5+
* - kind:'react' — real React (useState/map/onClick) + an injected data block,
6+
* executed by @object-ui/react-runtime. Gated by CAP_REACT_PAGES, enabled
7+
* here to exercise the trusted tier.
8+
* Tailwind v4 scans this file's text for class candidates, so the runtime
9+
* source strings below are fully styled. */
10+
import './index.css';
11+
import '@object-ui/components';
12+
import React from 'react';
13+
import { createRoot } from 'react-dom/client';
14+
import { SchemaRenderer } from '@object-ui/react';
15+
import { enableCapability, CAP_REACT_PAGES } from '@object-ui/core';
16+
17+
// Trusted tier on (a host would do this; never authored metadata).
18+
enableCapability(CAP_REACT_PAGES);
19+
20+
const htmlSource = `
21+
<section className="mx-auto max-w-3xl p-10">
22+
<h1 className="text-4xl font-bold tracking-tight text-slate-900">Release Notes</h1>
23+
<p className="mt-3 text-base leading-relaxed text-slate-500">A <strong className="font-semibold text-slate-700">kind:'html'</strong> page — plain native HTML tags, Tailwind classes, parsed (never executed).</p>
24+
<hr className="my-6 border-slate-200" />
25+
<h2 className="text-2xl font-semibold text-slate-800">What shipped</h2>
26+
<ul className="mt-3 list-disc space-y-2 pl-6 text-slate-600">
27+
<li>Full HTML tag set in the <em className="italic">html</em> tier.</li>
28+
<li>A trusted <a href="https://objectui.org" className="font-medium text-indigo-600 underline">react tier</a> behind a flag.</li>
29+
<li>Author writes markup; the platform renders it.</li>
30+
</ul>
31+
<blockquote className="mt-6 border-l-4 border-indigo-300 bg-indigo-50 p-4 text-slate-700">
32+
<p>“The best custom page is one the AI can write and the platform can render safely.”</p>
33+
</blockquote>
34+
<img src="https://placehold.co/600x160/4f46e5/ffffff?text=kind:html" alt="banner" className="mt-6 w-full rounded-xl" />
35+
</section>`;
36+
37+
const htmlPage = { type: 'home', kind: 'html', name: 'release_notes', label: 'Release Notes', source: htmlSource };
38+
39+
const reactSource = `
40+
function Page() {
41+
const [sortKey, setSortKey] = React.useState('amount');
42+
const [count, setCount] = React.useState(0);
43+
const rows = [...data].sort((a, b) => (sortKey === 'amount' ? b.amount - a.amount : a.name.localeCompare(b.name)));
44+
const total = data.reduce((s, r) => s + r.amount, 0);
45+
return (
46+
<div className="mx-auto max-w-3xl p-10">
47+
<div className="flex items-center justify-between">
48+
<h1 className="text-3xl font-bold text-slate-900">Pipeline (real React)</h1>
49+
<button
50+
onClick={() => setCount((c) => c + 1)}
51+
className="rounded-lg bg-indigo-600 px-4 py-2 text-sm font-semibold text-white hover:bg-indigo-500"
52+
>Clicked {count} times</button>
53+
</div>
54+
<p className="mt-2 text-sm text-slate-500">useState · map · reduce · sort · onClick — executed by the runtime.</p>
55+
<div className="mt-4 flex gap-2 text-xs">
56+
{['amount', 'name'].map((k) => (
57+
<button key={k} onClick={() => setSortKey(k)}
58+
className={'rounded-full px-3 py-1 font-semibold ' + (sortKey === k ? 'bg-slate-900 text-white' : 'bg-slate-100 text-slate-600')}
59+
>sort by {k}</button>
60+
))}
61+
</div>
62+
<table className="mt-4 w-full text-left text-sm">
63+
<thead><tr className="border-b text-slate-400"><th className="py-2">Account</th><th className="py-2 text-right">Amount</th></tr></thead>
64+
<tbody>
65+
{rows.map((r) => (
66+
<tr key={r.name} className="border-b border-slate-100">
67+
<td className="py-2 font-medium text-slate-700">{r.name}</td>
68+
<td className="py-2 text-right tabular-nums text-slate-600">{'$' + r.amount.toLocaleString()}</td>
69+
</tr>
70+
))}
71+
</tbody>
72+
</table>
73+
<div className="mt-3 text-right text-sm font-semibold text-slate-900">Total: {'$' + total.toLocaleString()}</div>
74+
</div>
75+
);
76+
}`;
77+
78+
const reactPage = {
79+
type: 'home',
80+
kind: 'react',
81+
name: 'pipeline_react',
82+
label: 'Pipeline',
83+
source: reactSource,
84+
data: [
85+
{ name: 'Acme', amount: 120000 },
86+
{ name: 'Globex', amount: 88000 },
87+
{ name: 'Initech', amount: 64000 },
88+
{ name: 'Umbrella', amount: 152000 },
89+
{ name: 'Soylent', amount: 41000 },
90+
],
91+
};
92+
93+
createRoot(document.getElementById('root')!).render(
94+
<React.StrictMode>
95+
<div className="space-y-10 bg-slate-50">
96+
<div data-tier="html"><SchemaRenderer schema={htmlPage as any} /></div>
97+
<div className="border-t-4 border-dashed border-slate-300" />
98+
<div data-tier="react"><SchemaRenderer schema={reactPage as any} /></div>
99+
</div>
100+
</React.StrictMode>,
101+
);
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/* Verifies the kind:'react' tier composing the REAL data components
2+
* (<ListView> + <ObjectForm>) with React state — a master/detail workbench,
3+
* the exact pattern of examples/app-showcase crm-workbench.page.ts. Rendered
4+
* through the real PageRenderer; a tiny in-memory adapter stands in for the
5+
* backend so the real plugins mount and the interaction is genuinely exercised. */
6+
import './index.css';
7+
import '@object-ui/components';
8+
import '@object-ui/plugin-grid';
9+
import '@object-ui/plugin-view';
10+
import '@object-ui/plugin-list';
11+
import '@object-ui/plugin-form';
12+
import '@object-ui/plugin-detail';
13+
import React from 'react';
14+
import { createRoot } from 'react-dom/client';
15+
import { SchemaRenderer, SchemaRendererProvider, AdapterCtx } from '@object-ui/react';
16+
import { I18nProvider } from '@object-ui/i18n';
17+
// NOTE: we do NOT call enableCapability here — `react-pages` is ON by default.
18+
// `?disable=1` simulates a server that set OS_PAGE_REACT=off (the runtime would
19+
// inject this global); the page then renders the "disabled" notice.
20+
if (new URLSearchParams(location.search).has('disable')) {
21+
(window as unknown as { __OBJECTUI_CAPABILITIES_DISABLED__?: string[] }).__OBJECTUI_CAPABILITIES_DISABLED__ = ['react-pages'];
22+
}
23+
24+
// ---- in-memory adapter (just enough for ListView + ObjectForm) ----
25+
let store: any[] = [
26+
{ id: '1', name: 'Apollo Migration', status: 'active', health: 'green', budget: 120000, owner: 'Dana Lee' },
27+
{ id: '2', name: 'Billing Revamp', status: 'planned', health: 'yellow', budget: 80000, owner: 'Sam Ortiz' },
28+
{ id: '3', name: 'Mobile App v2', status: 'active', health: 'red', budget: 210000, owner: 'Priya N.' },
29+
{ id: '4', name: 'Data Warehouse', status: 'on_hold', health: 'yellow', budget: 64000, owner: 'Chen Wu' },
30+
];
31+
let nextId = 5;
32+
const projectSchema = {
33+
name: 'showcase_project',
34+
label: 'Project',
35+
fields: {
36+
name: { type: 'text', label: 'Project Name', required: true },
37+
status: { type: 'select', label: 'Status', options: [
38+
{ label: 'Planned', value: 'planned' }, { label: 'Active', value: 'active' },
39+
{ label: 'On Hold', value: 'on_hold' }, { label: 'Completed', value: 'completed' } ] },
40+
health: { type: 'select', label: 'Health', options: [
41+
{ label: 'Green', value: 'green' }, { label: 'Yellow', value: 'yellow' }, { label: 'Red', value: 'red' } ] },
42+
budget: { type: 'currency', label: 'Budget' },
43+
owner: { type: 'text', label: 'Owner' },
44+
},
45+
};
46+
const adapter: any = {
47+
find: async () => store.slice(),
48+
findOne: async (_o: string, id: any) => store.find((r) => String(r.id) === String(id)) || null,
49+
create: async (_o: string, data: any) => { const rec = { id: String(nextId++), ...data }; store.push(rec); return rec; },
50+
update: async (_o: string, id: any, data: any) => {
51+
const i = store.findIndex((r) => String(r.id) === String(id)); if (i >= 0) store[i] = { ...store[i], ...data }; return store[i];
52+
},
53+
getObjectSchema: async () => projectSchema,
54+
};
55+
56+
const source = `
57+
function Page() {
58+
const adapter = useAdapter();
59+
const [selected, setSelected] = React.useState(null);
60+
const [mode, setMode] = React.useState('edit');
61+
const [reloadKey, setReloadKey] = React.useState(0);
62+
const [stats, setStats] = React.useState({ total: 0, active: 0 });
63+
const refreshStats = React.useCallback(async () => {
64+
if (!adapter) return;
65+
const all = await adapter.find('showcase_project', { top: 200 });
66+
const rows = Array.isArray(all) ? all : (all && all.records) || [];
67+
setStats({ total: rows.length, active: rows.filter((r) => r.status === 'active').length });
68+
}, [adapter]);
69+
React.useEffect(() => { refreshStats(); }, [refreshStats, reloadKey]);
70+
const openNew = () => { setSelected(null); setMode('create'); };
71+
const onRowClick = (rec) => { setSelected(rec); setMode('edit'); };
72+
const afterSave = () => { setSelected(null); setMode('edit'); setReloadKey((k) => k + 1); };
73+
const editing = mode === 'create' || selected;
74+
return (
75+
<div className="mx-auto max-w-6xl space-y-6 p-8">
76+
<header className="flex items-center justify-between">
77+
<div>
78+
<h1 className="text-2xl font-bold tracking-tight text-slate-900">CRM Workbench</h1>
79+
<p className="mt-1 text-sm text-slate-500">Real &lt;ListView&gt; + &lt;ObjectForm&gt; wired with React state.</p>
80+
</div>
81+
<button onClick={openNew} className="rounded-lg bg-indigo-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500">+ New project</button>
82+
</header>
83+
<div className="grid grid-cols-3 gap-4">
84+
<div className="rounded-xl border border-slate-200 bg-white p-4"><div className="text-xs font-medium uppercase tracking-wide text-slate-400">Total projects</div><div className="mt-1 text-3xl font-bold text-slate-900">{stats.total}</div></div>
85+
<div className="rounded-xl border border-slate-200 bg-white p-4"><div className="text-xs font-medium uppercase tracking-wide text-slate-400">Active</div><div className="mt-1 text-3xl font-bold text-emerald-600">{stats.active}</div></div>
86+
<div className="rounded-xl border border-slate-200 bg-white p-4"><div className="text-xs font-medium uppercase tracking-wide text-slate-400">Editing</div><div className="mt-1 truncate text-lg font-semibold text-slate-700">{mode === 'create' ? 'New project' : selected ? (selected.name || selected.id) : '—'}</div></div>
87+
</div>
88+
<div className="grid grid-cols-5 gap-6">
89+
<section className="col-span-3 rounded-xl border border-slate-200 bg-white p-2">
90+
<ListView key={reloadKey} objectName="showcase_project" fields={['name','status','health','budget','owner']} navigation={{ mode: 'none' }} onRowClick={onRowClick} />
91+
</section>
92+
<section className="col-span-2 rounded-xl border border-slate-200 bg-white p-5">
93+
{editing ? (
94+
<ObjectForm key={(mode === 'create' ? 'new' : selected && selected.id) + ':' + reloadKey} objectName="showcase_project" mode={mode} recordId={mode === 'edit' && selected ? selected.id : undefined} onSuccess={afterSave} onCancel={() => { setSelected(null); }} />
95+
) : (
96+
<div className="flex h-full min-h-[240px] flex-col items-center justify-center text-center text-slate-400"><div className="text-4xl">\u{1F5C2}\u{FE0F}</div><p className="mt-2 text-sm">Select a project to edit, or create a new one.</p></div>
97+
)}
98+
</section>
99+
</div>
100+
</div>
101+
);
102+
}`;
103+
104+
const page = { type: 'home', kind: 'react', name: 'crm_workbench', label: 'CRM Workbench', source };
105+
106+
createRoot(document.getElementById('root')!).render(
107+
<React.StrictMode>
108+
<I18nProvider>
109+
<AdapterCtx.Provider value={adapter}>
110+
<SchemaRendererProvider dataSource={adapter}>
111+
<div className="bg-slate-50"><SchemaRenderer schema={page as any} /></div>
112+
</SchemaRendererProvider>
113+
</AdapterCtx.Provider>
114+
</I18nProvider>
115+
</React.StrictMode>,
116+
);

apps/console/vite.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ const isCI = !!(process.env.VERCEL || process.env.CI);
6464
const workspaceAliases: Record<string, string> = {
6565
'@object-ui/components': path.resolve(__dirname, '../../packages/components/src'),
6666
'@object-ui/core': path.resolve(__dirname, '../../packages/core/src'),
67+
'@object-ui/react-runtime': path.resolve(__dirname, '../../packages/react-runtime/src'),
6768
'@object-ui/sdui-parser': path.resolve(__dirname, '../../packages/sdui-parser/src'),
6869
'@object-ui/fields': path.resolve(__dirname, '../../packages/fields/src'),
6970
'@object-ui/layout': path.resolve(__dirname, '../../packages/layout/src'),

0 commit comments

Comments
 (0)