···8787 if (Array.isArray(node)) return <>{await Promise.all(node.map(n => renderNode(n, ctx)))}</>;
88888989 if (isValidElement(node)) {
9090- const { resolved, node: out, context: outCtx } = await render(
9191- node, ctx, { resolver }
9292- );
9393- if (resolved && isValidElement(out)) {
9494- const Primitive = primitives[out.type];
9595- if (!Primitive) return <>{`<!-- unknown: ${out.type} -->`}</>;
9696- return Primitive({ props: out.props ?? {}, ctx: outCtx });
9090+ const result = await render(node, ctx, { resolver });
9191+ if (result.node === null) {
9292+ // Primitive: host renders using element type + result.props
9393+ const Primitive = primitives[node.type];
9494+ if (!Primitive) return <>{`<!-- unknown: ${node.type} -->`}</>;
9595+ return Primitive({ props: result.props, ctx: result.context });
9796 }
9898- return renderNode(out, outCtx);
9797+ return renderNode(result.node, result.context);
9998 }
10099 return <>{String(node)}</>;
101100}
···146145147146## How it works
148147149149-`render()` resolves one element at a time. It returns `resolved: true` when the element is a primitive (the host handles it) or `resolved: false` when the element expanded into a subtree that needs more passes. The walk loop drives this to completion.
148148+`render()` resolves one element at a time. It returns `node: null` when the element is a primitive (the host renders it using `element.type` + `result.props`) or a non-null node tree when the element expanded into a subtree that needs more passes. The walk loop drives this to completion.
150149151151-1. **Binding resolution** — `at.inlay.Binding` elements in props are resolved against the current scope.
150150+1. **Binding resolution** — `at.inlay.Binding` elements in props are resolved against the current scope. The concrete values are available on `result.props`.
1521512. **Type lookup** — the element's NSID is looked up across the import stack. First pack that exports the type wins.
1531523. **Component rendering** — depends on the component's body:
154154- - **No body** — a primitive; returned as-is.
153153+ - **No body** — a primitive; returns `node: null` with resolved props.
155154 - **Template** — element tree expanded with prop bindings.
156155 - **External** — XRPC call to the component's service. Children are passed as slots and restored from the response.
157157-4. **Error handling** — errors become `at.inlay.Throw` elements. `MissingError` propagates for `at.inlay.Maybe` to catch.
156156+4. **Error handling** — errors throw with `componentStack` stamped on the error. `MissingError` propagates for `at.inlay.Maybe` to catch.
158157159158## API
160159···173172174173- **`Resolver`** — `{ fetchRecord, xrpc, resolveLexicon }`
175174- **`RenderContext`** — `{ imports, component?, componentUri?, depth?, scope?, stack? }`
176176-- **`RenderResult`** — `{ resolved, node, context, cache? }`
175175+- **`RenderResult`** — `{ node, context, props, cache? }`
177176- **`RenderOptions`** — `{ resolver, maxDepth? }`
178177- **`ComponentRecord`** — re-exported from generated lexicon defs
179178- **`CachePolicy`** — re-exported from generated lexicon defs