React in a cell
A cell is one unit of a scene: data, plus the view that shows it. The view is a self-contained HTML document, which is enough to run React from a CDN with no build step. The two things an external app has to wire up are already there: the data is injected, and the auth is ambient. So a React cell is usually shorter than the external version of the same component.
The runtime injects data and auth
The renderer puts three globals beside your HTML, so the document never contains data or credentials:
| Global | What it is |
|---|---|
window.__DASLAB_WIDGET_DATA__ | This view's fields.data, inlined so the first render needs no round-trip. |
window.__DASLAB_WIDGET_CTX__ | Scene id, asset id, host, and a session scoped to the signed-in viewer. |
window.daslab / window.daslabReady | The typed client factory. d.call('<tool>', input) runs a tool as the signed-in viewer, with no token to pass. |
A display-only render, like a public snapshot, carries no session: the first d.call throws a "display-only" error.
A cell that searches flights
Here is a complete cell. It renders whatever data the scene last committed, then fetches fresh prices:
<div id="root"></div>
<script type="module">
import React from "https://esm.sh/react@19";
import { createRoot } from "https://esm.sh/react-dom@19/client";
const { useState, useEffect } = React;
const h = React.createElement;
function Flights() {
const [data, setData] = useState(window.__DASLAB_WIDGET_DATA__ ?? null);
useEffect(() => {
(async () => {
const d = (await window.daslabReady)();
const r = await d.call("daslab_search_flights",
{ from: "BER", to: "BKK", date: "2026-09-12" });
setData(JSON.parse(r.result.content[0].text));
})();
}, []);
if (!data) return h("p", null, "searching…");
return h("ul", null, data.results.slice(0, 3).map((f) =>
h("li", { key: `${f.departure}-${f.price}` },
`${Math.round(f.price)} · ${f.airlines.join(", ")} · ${f.stops === 0 ? "nonstop" : f.stops + " stops"}`)
));
}
createRoot(document.getElementById("root")).render(h(Flights));
</script>
useState(window.__DASLAB_WIDGET_DATA__)renders the committed data instantly and the effect fetches fresh, so the tile is never blank.d.call(tool, input)is the same tool surface the agent tools and the external SDK use. Multi-account tools take anaccountIdin the input.- One esm.sh URL per lib, version pinned, never
@latest: it's a redirect with no long cache and can break under you. esm.sh resolves peer deps, so you don't hand-wire React into ReactDOM.
JSX belongs in your toolchain
The example writes hyperscript (h(…)) instead of JSX. In-browser Babel is the one transform that makes a cell slow; skip it. If you want JSX, author the cell as a normal React app in your own toolchain and paste the built output: the injected globals are identical either way, and the same component runs in a view and in an external app.
The SDK hooks wrap the same bridge
The useEffect and d.call bridge above is dependency-light and fine. The @daslab/sdk React hooks (useDaslabCall, useScene, <Widget/>) wrap it with caching, dedup, and refetch. <Widget data={…}/> renders any widget payload with no transport, so it drops into a cell over d.call results today.
Updated 2026-08-06