@weftui/dom 0.26.0 → 0.26.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,496 @@
1
+ ---
2
+ title: "@weftui/core"
3
+ order: 1
4
+ section: reference
5
+ description: Full API surface for @weftui/core — element builders, components, sources, streams, and boundaries.
6
+ ---
7
+
8
+ # @weftui/core API Reference
9
+
10
+ ## Element builders
11
+
12
+ ### `h`
13
+
14
+ Proxy-based namespace for building HTML and SVG elements. Every property is an element builder for that tag name:
15
+
16
+ ```typescript
17
+ import { h } from "@weftui/core";
18
+
19
+ h.div(props, children)
20
+ h.span(props, child: string | number)
21
+ h.input(props)
22
+ h.ul(children)
23
+ // ...any HTML or SVG tag
24
+ ```
25
+
26
+ Each builder has these overloads:
27
+
28
+ | Signature | Description |
29
+ | --------------------------------------- | --------------------------- |
30
+ | `h.tag(props, children: Renderable[])` | Props + array of children |
31
+ | `h.tag(props, child: string \| number)` | Props + single static child |
32
+ | `h.tag(props)` | Props only, no children |
33
+ | `h.tag(children: Renderable[])` | Children only, no props |
34
+ | `h.tag(child: string \| number)` | Single static child only |
35
+ | `h.tag()` | No arguments |
36
+
37
+ **Return type**: `Node<PropsE<P> | ChildrenE<C>, PropsR<P> | ChildrenR<C>>`
38
+
39
+ Reactive prop values (Stream, Effect, Subscribable) contribute their `E`/`R` to the node. Static values contribute `never`. Children's channels are unioned with props' channels.
40
+
41
+ ### `h.fragment`
42
+
43
+ Groups children into a fragment that renders without a wrapper element:
44
+
45
+ ```typescript
46
+ import { h } from "@weftui/core";
47
+
48
+ h.fragment(children: Node[]): Node<ChildrenE, ChildrenR>
49
+ ```
50
+
51
+ Use when a component needs to return multiple sibling elements.
52
+
53
+ ### `Component`
54
+
55
+ Namespace exposing two factories for reusable components with caller-propagating reactive prop types: `Component.gen` (generator body) and `Component.make` (plain-function body). Both return a callable that is generic over the caller's specific `props`/`children`, so reactive prop values and reactive children contribute their `E`/`R` at the call site.
56
+
57
+ ```typescript
58
+ import { Component } from "@weftui/core";
59
+
60
+ Component.gen<BaseProps, C>(
61
+ body: (props: BaseProps, children: C) => Generator<YieldedEffect, ElementDescriptor, never>
62
+ ): <GenP extends BaseProps, GenC extends C>(
63
+ props: GenP,
64
+ children?: GenC,
65
+ ) => Node<PropsE<GenP> | ChildrenE<…> | BodyE, PropsR<GenP> | ChildrenR<…> | BodyR>;
66
+
67
+ Component.make<BaseProps, C>(
68
+ body: (props: BaseProps, children: C) => Effect<ElementDescriptor, E, R>
69
+ ): /* same call signature as above */;
70
+ ```
71
+
72
+ **`Children`** — the optional `children` argument may be either form:
73
+
74
+ ```typescript
75
+ type Component.Children<Input = never> =
76
+ | readonly Renderable[]
77
+ | ((input: Input) => readonly Renderable[]);
78
+ ```
79
+
80
+ For function-children, `ChildrenE`/`ChildrenR` are extracted from the function's `ReturnType`, not from the function itself. The component's body invokes the function with whatever input it chooses.
81
+
82
+ **Example — `Component.gen`**:
83
+
84
+ ```typescript
85
+ interface TextFieldProps {
86
+ value?: Source.Source<string>;
87
+ onChange?: (v: string) => void;
88
+ }
89
+
90
+ const TextField = Component.gen(function* (props: TextFieldProps) {
91
+ const value = yield* Source.toSubscribable(props.value);
92
+ return yield* h.input({
93
+ value,
94
+ oninput: (e) => props.onChange?.(e.currentTarget.value),
95
+ });
96
+ });
97
+ ```
98
+
99
+ **Example — `Component.make` with function-children**:
100
+
101
+ ```typescript
102
+ const Labeled = Component.make(
103
+ (props: { label: string }, children: (label: string) => readonly Renderable[]) =>
104
+ h.div({ class: "field" }, children(props.label)),
105
+ );
106
+
107
+ Labeled({ label: "Name" }, (label) => [h.label(label), h.input()]);
108
+ ```
109
+
110
+ > For rendering a reactive collection, reach for the built-in [`List.each`](#listeach)
111
+ > rather than mapping items by hand — it reconciles by key across emissions instead of
112
+ > rebuilding the region.
113
+
114
+ ### `Boundary` namespace
115
+
116
+ Variants for intercepting rendering-path errors in a subtree, plus `Boundary.suspend` for async fallbacks and `Boundary.rpc` for rpc-backed server data. Each returns a descriptor that the renderer processes via the same `{ type, props }` branch. The catch variants share the same call shape — props first, children array second.
117
+
118
+ **What is caught:**
119
+
120
+ - Construction-time failures — the Effect phase of building child nodes
121
+ - Post-mount stream failures — streams driving children or prop values that fail after mount
122
+
123
+ **What is NOT caught:** event handler errors (they run in detached fibers outside the render path).
124
+
125
+ ```typescript
126
+ import { Boundary } from "@weftui/core";
127
+ ```
128
+
129
+ #### `Boundary.suspend`
130
+
131
+ Shows a fallback while async children are pending:
132
+
133
+ ```typescript
134
+ Boundary.suspend(
135
+ props: { fallback?: Renderable },
136
+ children: Node[]
137
+ ): Node<ChildrenE, ChildrenR>
138
+ ```
139
+
140
+ - Shows `fallback` while any registered child has not yet emitted its first value
141
+ - Performs a single atomic DOM swap once all children have settled
142
+ - Works on both the server (streaming patch model) and the client
143
+ - `hydrate()` sees through `Boundary.suspend` boundaries and adopts resolved DOM in place
144
+
145
+ #### `Boundary.rpc`
146
+
147
+ A universal server/client render boundary backed by one `Rpc` from the app's merged `RpcGroup` ([`@effect/rpc`](https://github.com/Effect-TS/effect/tree/main/packages/rpc)). The rpc **`_tag`** is the boundary's stable identity and its **payload schema** the typed input; the handler lives in the server-only rpc Layer (`group.toLayer(...)`), which the client never imports — tree-shaking does the client/server split structurally. Unlike the catch variants it takes a `render` function — not a children array — and that `render` receives a reactive [`Resource`](#resourcea), not a bare value.
148
+
149
+ ```typescript
150
+ Boundary.rpc<R extends Rpc.Any, C extends Node<any, any>>(
151
+ rpc: R, // an Rpc from the merged RpcGroup; its _tag + schemas drive the boundary
152
+ payload: () => Rpc.Payload<R>, // thunk: a fresh typed payload per call (SSR / refetch / mount)
153
+ render: (resource: Resource<Rpc.Success<R>>) => C, // builds the subtree from a reactive Resource (not a bare value)
154
+ options?: { fallback?: Renderable }, // shown only during a client-first SPA mount
155
+ ): Node<Node.Error<C> | Rpc.Error<R>, Node.Context<C>>
156
+ ```
157
+
158
+ The boundary resolves the rpc through the ambient [`AppRpcClientTag`](#apprpcclienttag) seam, provided by `@weftui/router` (`RouterServer` on the server, `RouterLive` on the client). It has four lifecycles:
159
+
160
+ - **SSR:** the server resolves the rpc in-process (over the handler Layer), `successSchema`-encodes the result inline as `<script type="application/json">` at the region cursor, then renders `render(seededResource)` to HTML in place.
161
+ - **Hydrate:** `hydrate` reads the inline payload positionally, `successSchema`-decodes it, seeds the `Resource`, and adopts the DOM — it **never re-calls the rpc** (replay, not refetch).
162
+ - **Refetch:** `resource.refetch` calls the rpc again over the network (`POST /_eui/rpc`) and patches the subtree in place (**stale-on-error** — a failed refetch leaves the previous value intact).
163
+ - **Client-first mount:** SPA-navigating into a boundary with **no** SSR payload renders `options.fallback`, forks the rpc call, and swaps in `render(resource)` once it resolves.
164
+
165
+ **Channel algebra:** the output `E` is `render`'s error union plus the rpc's typed `Rpc.Error<R>` (`never` for an rpc with no `error` schema). The output `R` is **exactly** `render`'s `R`, untouched — there is no `provide`/`RServer` to discharge (the handler lives in the rpc Layer) and **no `Exclude`** is applied. A server-only tag accidentally referenced in `render` therefore stays in `R`, where `hydrate`'s `AssertNoServerOnly` rejects it. Brand such services with [`ServerTag`](#servertag).
166
+
167
+ **Typed-failure replay:** a resolved rpc **error** on the SSR pass is `errorSchema`-encoded and relocated to the nearest enclosing failure `Boundary`, then replayed on the client (decoded and re-raised, reproducing the same fallback DOM — never retried). A transport **defect**, or an rpc with no `error` schema, is not replayed; it propagates.
168
+
169
+ > **Not yet covered:** streamed success (`Rpc.make(..., { stream: true })`) and mutations are on the roadmap, not this pass.
170
+
171
+ ##### `Resource<A>`
172
+
173
+ The reactive handle `render` receives (`A = Rpc.Success<R>`). After hydrate the region is live: `value` is seeded with the SSR payload and the client can `refetch` the same data on demand, patching the rendered subtree in place.
174
+
175
+ | Field | Type | Meaning |
176
+ | --------- | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
177
+ | `value` | `Subscribable.Subscribable<A>` | Current data. Seeded with the SSR `data` (await-first, emits immediately, so SSR HTML and adopted DOM are byte-identical — no fallback flash). A successful refetch pushes the new value. |
178
+ | `refetch` | `Effect.Effect<void>` | Re-resolves the rpc over the network with a fresh `payload()` and sets `value`. Client only — a no-op on the server. |
179
+ | `pending` | `Subscribable.Subscribable<boolean>` | `true` while a refetch is in flight (`false` on the server / before any refetch). |
180
+ | `error` | `Subscribable.Subscribable<Option<unknown>>` | `Some` with the last refetch error, else `None`. A failed refetch is stale-on-error — it does **not** unmount or raise into a failure `Boundary`. |
181
+
182
+ ##### `RpcOptions`
183
+
184
+ ```typescript
185
+ interface RpcOptions {
186
+ readonly fallback?: Renderable; // shown only during a client-first mount; omit/null renders nothing while pending
187
+ }
188
+ ```
189
+
190
+ ##### `SERVER_BOUNDARY`
191
+
192
+ ```typescript
193
+ export const SERVER_BOUNDARY: unique symbol;
194
+ ```
195
+
196
+ The descriptor `type` every `Boundary.rpc` carries (`{ type: SERVER_BOUNDARY, props }`). Exported for renderers, which detect and handle the boundary synchronously via the `{ type, props }` branch without running the node.
197
+
198
+ ##### `AppRpcClientTag`
199
+
200
+ ```typescript
201
+ interface AppRpcClient {
202
+ readonly call: (tag: string, payload: unknown) => Effect.Effect<unknown, unknown>;
203
+ }
204
+ class AppRpcClientTag extends Context.Tag("@weftui/core/AppRpcClient")<
205
+ AppRpcClientTag,
206
+ AppRpcClient
207
+ >() {}
208
+ ```
209
+
210
+ The ambient, package-neutral seam the renderer resolves a `Boundary.rpc` through — a **flat, untyped** caller `(tag, payload) => Effect<success>`. It lets `@weftui/dom` resolve a boundary without importing `@effect/rpc` or `@weftui/router`. `@weftui/router` provides it: a **network** `RpcClient` (POST `/_eui/rpc`) in the browser, an **in-process** client over the handler Layer on the server. `call` returns the already-decoded success; the renderer owns `successSchema`/`errorSchema` decoding of the inline SSR payload only. Both `AppRpcClientTag` and the `AppRpcClient` type are re-exported from `@weftui/core`. Absent in a router-less mount, where a `Boundary.rpc` resolves to a descriptive "needs router/rpc" error (not a defect).
211
+
212
+ See the [rpc data boundaries guide](https://weftui.dev/docs/how-to/load-data-with-rpc) and [examples/router-ssr](https://github.com/stefvw93/weft/tree/main/examples/router-ssr).
213
+
214
+ #### `Boundary.catchAll`
215
+
216
+ Catches all typed failures (`Cause.fail`). Defects (`Cause.die`) are not caught and re-raise.
217
+
218
+ ```typescript
219
+ Boundary.catchAll<C, FE, FR>(
220
+ props: { fallback: (e: ChildrenE<C>) => Node<FE, FR> },
221
+ children: C,
222
+ ): Node<FE, ChildrenR<C> | FR>
223
+ ```
224
+
225
+ The children's `E` is fully consumed. The output `E` is only the fallback's own error channel.
226
+
227
+ #### `Boundary.catchAllCause`
228
+
229
+ Catches every `Cause` including defects and interruptions.
230
+
231
+ ```typescript
232
+ Boundary.catchAllCause<C, FE, FR>(
233
+ props: { fallback: (cause: Cause.Cause<ChildrenE<C>>) => Node<FE, FR> },
234
+ children: C,
235
+ ): Node<FE, ChildrenR<C> | FR>
236
+ ```
237
+
238
+ The fallback receives the full `Cause`, not just the failure value.
239
+
240
+ #### `Boundary.catchTag`
241
+
242
+ Catches errors whose `_tag` equals `props.tag`. Unmatched errors re-raise to the nearest parent boundary.
243
+
244
+ ```typescript
245
+ Boundary.catchTag<C, Tag, FE, FR>(
246
+ props: {
247
+ tag: Tag; // must be a key of ChildrenE<C>["_tag"]
248
+ fallback: (e: Extract<ChildrenE<C>, { _tag: Tag }>) => Node<FE, FR>;
249
+ },
250
+ children: C,
251
+ ): Node<Exclude<ChildrenE<C>, { _tag: Tag }> | FE, ChildrenR<C> | FR>
252
+ ```
253
+
254
+ The matched tag is removed from the output `E` union.
255
+
256
+ #### `Boundary.catchTags`
257
+
258
+ Catches multiple tags in one call. The handlers record IS the first argument (no wrapping object). Unregistered tags re-raise.
259
+
260
+ ```typescript
261
+ Boundary.catchTags<C, Handlers>(
262
+ handlers: {
263
+ [Tag in ChildrenE<C>["_tag"]]?: (e: Extract<ChildrenE<C>, { _tag: Tag }>) => Node<any, any>
264
+ },
265
+ children: C,
266
+ ): Node<UnhandledE | HandlersE, ChildrenR<C> | HandlersR>
267
+ ```
268
+
269
+ #### `Boundary.catchSome`
270
+
271
+ The fallback returns `Option<Node>`. `Option.none()` re-raises the error; `Option.some(node)` catches it.
272
+
273
+ ```typescript
274
+ Boundary.catchSome<C, FE, FR>(
275
+ props: { fallback: (e: ChildrenE<C>) => Option.Option<Node<FE, FR>> },
276
+ children: C,
277
+ ): Node<ChildrenE<C> | FE, ChildrenR<C> | FR>
278
+ ```
279
+
280
+ The children's `E` is preserved in the output because the boundary may or may not handle any given error.
281
+
282
+ #### `Boundary.catchIf`
283
+
284
+ A predicate gates the fallback. `false` re-raises.
285
+
286
+ ```typescript
287
+ Boundary.catchIf<C, FE, FR>(
288
+ props: {
289
+ predicate: (e: ChildrenE<C>) => boolean;
290
+ fallback: (e: ChildrenE<C>) => Node<FE, FR>;
291
+ },
292
+ children: C,
293
+ ): Node<ChildrenE<C> | FE, ChildrenR<C> | FR>
294
+ ```
295
+
296
+ #### Re-raise and nesting
297
+
298
+ When a boundary's `match` returns `null` (unmatched error), the error propagates to the nearest **parent** `Boundary` via `BoundaryContext`. If there is no parent boundary, the error fails the enclosing mount.
299
+
300
+ Inner boundaries shadow outer ones for their subtree — the innermost boundary is always tried first.
301
+
302
+ ```typescript
303
+ // Inner catches FooError; BarError propagates to outer
304
+ Boundary.catchAll({ fallback: (e) => h.div(`Outer: ${e.message}`) }, [
305
+ Boundary.catchTag({ tag: "Foo", fallback: (e) => h.span(`Foo: ${e.msg}`) }, [
306
+ ChildWithFooOrBarError(),
307
+ ]),
308
+ ]);
309
+ ```
310
+
311
+ ---
312
+
313
+ ## ServerTag
314
+
315
+ A `Context.Tag` whose identifier is branded server-only. Use it exactly like `Context.Tag` for services that must only ever be provided on the server — e.g. a database handle read inside an rpc handler Layer. The brand also guards [`Boundary.rpc`](#boundaryrpc): a server-only tag accidentally referenced in `render` stays in the requirement channel, where `hydrate`'s `AssertNoServerOnly` rejects it at compile time.
316
+
317
+ ```typescript
318
+ import { ServerTag } from "@weftui/core";
319
+ import { Effect } from "effect";
320
+
321
+ class Database extends ServerTag("Database")<
322
+ Database,
323
+ { readonly getProduct: () => Effect.Effect<Product> }
324
+ >() {}
325
+ ```
326
+
327
+ - The server-only brand rides along in the requirement channel `R` of any effect that uses the tag.
328
+ - An rpc handler Layer (`group.toLayer(...)`) discharges it on the server, where it is provided; it never enters a [`Boundary.rpc`](#boundaryrpc)'s output `R`, since `render` only reads the decoded result.
329
+ - If a branded tag ever reaches client code — referenced in `render` and surviving into `hydrate`'s requirement channel — `AssertNoServerOnly` resolves `R` to a compile-error sentinel (`ServerOnlyLeak`) at the `hydrate` call site, rather than failing silently at runtime.
330
+
331
+ `ServerOnly`, `ServerOnlyLeak`, and `AssertNoServerOnly<R>` are exported alongside `ServerTag` for advanced typing; most code only needs `ServerTag` itself.
332
+
333
+ ---
334
+
335
+ ## Keyed lists
336
+
337
+ ### `List` namespace
338
+
339
+ The keyed-list combinator. It is the opt-in alternative to wholesale child rebuilds: items are rendered **once per key** and reconciled across emissions, so reordering, inserting, or removing items reuses and moves existing DOM rather than rebuilding the region.
340
+
341
+ > **Note:** This exported `List` namespace is the built-in, key-reconciling way to
342
+ > render collections — prefer it over hand-rolling a component that maps items into
343
+ > elements.
344
+
345
+ ```typescript
346
+ import { h, List } from "@weftui/core";
347
+
348
+ h.ul([List.each({ of: rows.changes, by: (row) => row.id }, (row) => h.li(row.name))]);
349
+ ```
350
+
351
+ #### `List.each`
352
+
353
+ Declares a keyed reactive list region.
354
+
355
+ ```typescript
356
+ List.each<S extends Source.Source<Iterable<any>, any, any>, CE, CR, K>(
357
+ options: List.Options<S, K>,
358
+ render: (item: ItemOf<S>, index: number) => Node<CE, CR>,
359
+ ): Node<Source.Error<S> | CE, Source.Context<S> | CR>
360
+ ```
361
+
362
+ `render` runs **once per key**; a persisted key keeps its DOM nodes and its running subscription fibers across re-emits (it is never re-invoked). The returned node's `E`/`R` are the union of the source channels and the channels of the node `render` returns.
363
+
364
+ > **⚠️ Render-once / index-key footgun:** because `render` runs exactly once per key, reconciliation never refreshes a kept row's content — refresh a row by threading a `Stream` **inside** it, not by re-running `render`. Keying by index (`by: (_, i) => i`) reuses rows positionally and will show stale content after a reorder; prefer a stable identity key (`by: (item) => item.id`).
365
+
366
+ **`List.Options<S, K>`**
367
+
368
+ ```typescript
369
+ interface List.Options<S, K> {
370
+ readonly of: S; // static Iterable<T>, or an Effect/Stream/Subscribable of one
371
+ readonly by?: (item: ItemOf<S>, index: number) => K; // key projection; compared via Effect Equal / Hash
372
+ }
373
+ ```
374
+
375
+ - **`of`** — the list source. Each emission is materialized to an array to fix order, then reconciled by key.
376
+ - **`by`** — projects each item to its reconciliation key. Omitted ⇒ the item itself is the key (structural for `Data`, by reference otherwise).
377
+
378
+ #### `List.Error<N>` and `List.Context<N>`
379
+
380
+ Type-level accessors that extract the `E` and `R` channels from a list `Node`. Re-exported from the canonical `Node.Error` / `Node.Context` accessors.
381
+
382
+ See the `examples/keyed-list` example for a full reconciliation walkthrough (focus, uncontrolled inputs, and per-row counters surviving reorders).
383
+
384
+ ---
385
+
386
+ ## Types
387
+
388
+ ### `Node<E, R>`
389
+
390
+ ```typescript
391
+ type Node<E = never, R = never> = Effect.Effect<ElementDescriptor, E, R>;
392
+ ```
393
+
394
+ The core tree type. Every element builder and component returns a `Node`. Because `Node` is an alias for `Effect.Effect`, all Effect operators work on nodes directly.
395
+
396
+ ### `Source` namespace
397
+
398
+ The `Source` namespace contains the reactive prop vocabulary type and its normalization utility:
399
+
400
+ ```typescript
401
+ import { Source } from "@weftui/core";
402
+
403
+ // The type union
404
+ type Source.Source<A, E, R> = A | Effect.Effect<A, E, R> | Stream.Stream<A, E, R> | Subscribable<A, E, R>
405
+ ```
406
+
407
+ Any prop or child that supports reactivity accepts a `Source.Source`. Static values, Effects, Streams, and Subscribables are all valid.
408
+
409
+ #### `Source.toSubscribable(source, key?)`
410
+
411
+ Normalizes any `Source.Source` into a hot `Subscribable<A, E | NoPropValue, R>` scoped to the enclosing `Scope`:
412
+
413
+ ```typescript
414
+ Source.toSubscribable<A, E, R>(
415
+ source: Source.Source<A, E, R>,
416
+ key?: string
417
+ ): Effect.Effect<Subscribable<A, E | NoPropValue, R>, never, Scope>
418
+ ```
419
+
420
+ Normalization rules:
421
+
422
+ - **`Subscribable`** → returned by reference, no new ref or fiber
423
+ - **Static value** → `get` succeeds immediately; `changes` emits once
424
+ - **`Effect`** → memoized via `Effect.cached`; `changes` emits the resolved value once
425
+ - **`Stream`** → forks a scoped pump fiber that drains into a `SubscriptionRef`; `get` awaits the first emission
426
+
427
+ The pump fiber is tied to the enclosing scope via `Effect.forkScoped` — it terminates when the scope closes.
428
+
429
+ ### `NoPropValue`
430
+
431
+ Tagged error raised when a `Stream` prop ends before emitting a value:
432
+
433
+ ```typescript
434
+ class NoPropValue extends Data.TaggedError("NoPropValue")<{
435
+ readonly key?: string;
436
+ }> {}
437
+ ```
438
+
439
+ The `key` field identifies which prop triggered the error when provided.
440
+
441
+ ### `PropsE<P>` and `PropsR<P>`
442
+
443
+ Type-level utilities that extract the `E` and `R` channels from a props object:
444
+
445
+ ```typescript
446
+ type PropsE<P> = { [K in keyof P]: P[K] extends Stream.Stream<any, infer E, any> ? E : ... }[keyof P]
447
+ type PropsR<P> = { [K in keyof P]: P[K] extends Stream.Stream<any, any, infer R> ? R : ... }[keyof P]
448
+ ```
449
+
450
+ These are used internally by `h` and `Component` to accumulate channels from props. You generally don't need to reference them directly unless building utilities over the combinator API.
451
+
452
+ ---
453
+
454
+ ## Constants
455
+
456
+ ### `FRAGMENT`
457
+
458
+ Internal brand used to mark fragment nodes. Not intended for direct use — use `h.fragment` instead.
459
+
460
+ ---
461
+
462
+ ## Utility functions
463
+
464
+ ### `isStream(value)`
465
+
466
+ Returns `true` if `value` is a `Stream.Stream`:
467
+
468
+ ```typescript
469
+ isStream(value: unknown): value is Stream.Stream<unknown, unknown, unknown>
470
+ ```
471
+
472
+ ### `toStream(value)`
473
+
474
+ Normalizes a static value, `Effect`, or `Stream` into a `Stream`:
475
+
476
+ ```typescript
477
+ toStream<A>(value: A | Effect.Effect<A> | Stream.Stream<A>): Stream.Stream<A>
478
+ ```
479
+
480
+ - Static value → `Stream.make(value)` (single emission)
481
+ - `Effect` → `Stream.fromEffect(effect)` (one-shot)
482
+ - `Stream` → returned as-is
483
+
484
+ ### `isSubscribable(value)`
485
+
486
+ Returns `true` if `value` implements the `Subscribable` interface (keyed off `Subscribable.TypeId`):
487
+
488
+ ```typescript
489
+ isSubscribable(value: unknown): value is Subscribable<unknown, unknown, unknown>
490
+ ```
491
+
492
+ ## See also
493
+
494
+ - [The Combinator API](https://weftui.dev/docs/explanation/combinator-api) · [Reactive Primitives](https://weftui.dev/docs/explanation/reactive-primitives) · [Boundaries and Suspense](https://weftui.dev/docs/explanation/boundaries-and-suspense) — the concepts behind this surface
495
+ - [Author Components](https://weftui.dev/docs/how-to/author-components) · [Render Keyed Lists](https://weftui.dev/docs/how-to/render-keyed-lists) — task guides that use it
496
+ - [`@weftui/dom` reference](https://weftui.dev/docs/reference/dom) · [`@weftui/router` reference](https://weftui.dev/docs/reference/router)
@@ -0,0 +1,142 @@
1
+ ---
2
+ title: "@weftui/dom"
3
+ order: 2
4
+ section: reference
5
+ description: Full API surface for @weftui/dom — the client renderer (mount, hydrate) and the server renderer (renderToString and streaming variants).
6
+ ---
7
+
8
+ # @weftui/dom API Reference
9
+
10
+ The DOM renderer for Weft. It has two entry points — `@weftui/dom/client` for the
11
+ browser and `@weftui/dom/server` for Node — plus a package root that re-exports the
12
+ renderer error types. See the [Server-Side Rendering guide](https://weftui.dev/docs/how-to/render-on-the-server)
13
+ for a narrative walkthrough.
14
+
15
+ ## `@weftui/dom/client`
16
+
17
+ ### `mount`
18
+
19
+ ```ts
20
+ mount(node: Renderable, target: Element): Effect<MountHandle, RenderError, R>
21
+ ```
22
+
23
+ Renders a Weft `node` into `target` for a fresh (non-SSR) page, building real DOM
24
+ and starting every reactive stream. Returns a `MountHandle` whose scope owns the
25
+ mounted tree; closing it tears the tree down. Use `mount` for purely client-rendered
26
+ apps; use `hydrate` when the markup already exists from SSR.
27
+
28
+ ### `hydrate`
29
+
30
+ ```ts
31
+ hydrate(node: Renderable, target: Element): Effect<MountHandle, HydrationMismatchError | RenderError, R>
32
+ ```
33
+
34
+ Adopts server-rendered DOM **in place** inside `target` and resumes reactivity
35
+ without re-creating elements. The `node` must produce a tree structurally identical
36
+ to what the server rendered; a divergence fails with `HydrationMismatchError`. This
37
+ is the flash-free path: no second render, the existing nodes simply become live.
38
+
39
+ ### `mountScoped`
40
+
41
+ ```ts
42
+ mountScoped(app: Renderable, root: HTMLElement): Effect<MountHandle, UnsupportedNodeTypeError | StreamSubscriptionError | RenderError, Scope.Scope>
43
+ ```
44
+
45
+ Scope-aware `mount`: identical behavior, but requires an ambient `Scope.Scope` in
46
+ `R` and registers `unmount` as a finalizer on it, so the mount lives until that
47
+ scope closes rather than only until the mount effect resolves. Provide any scoped
48
+ layer **outside** a long-lived scoped region so it outlives initial render — see
49
+ [Provide Services](https://weftui.dev/docs/how-to/provide-services) for the composition and
50
+ [Layer lifetime at the mount](https://weftui.dev/docs/explanation/services-and-context#layer-lifetime-at-the-mount)
51
+ for why.
52
+
53
+ ### `hydrateScoped`
54
+
55
+ ```ts
56
+ hydrateScoped(app: Renderable, root: HTMLElement): Effect<MountHandle, UnsupportedNodeTypeError | StreamSubscriptionError | RenderError | HydrationMismatchError, Scope.Scope>
57
+ ```
58
+
59
+ Scope-aware `hydrate` — same relationship as `mountScoped` to `mount`, with
60
+ `hydrate`'s error union (`HydrationMismatchError` added) and the same client-only
61
+ compile-time guard: a server-only requirement left in `app`'s `R` degrades the
62
+ return type to `ServerOnlyLeak` via `AssertNoServerOnly`.
63
+
64
+ ### `MountHandle`
65
+
66
+ The handle returned by `mount`, `hydrate`, `mountScoped`, and `hydrateScoped`. Its
67
+ `unmount()` interrupts every subscription and event handler and disposes the
68
+ mount's `ManagedRuntime`; it does **not** remove the mounted DOM nodes from `root`.
69
+ `unmount` is idempotent — safe to call more than once, including once
70
+ automatically and once explicitly.
71
+
72
+ The runtime backing the handle lives until `unmount` runs, not until the
73
+ `mount`/`hydrate` effect resolves — that effect completes right after initial
74
+ render, while streams and handlers keep running in the background. If `mount` or
75
+ `hydrate` runs inside a region that supplies an ambient `Scope.Scope` (e.g. under
76
+ `Effect.scoped`), `unmount` is auto-registered on that scope as a finalizer, so the
77
+ mount tears down when the scope closes; with no ambient scope, behavior is
78
+ unchanged and `unmount` must be called explicitly. `mountScoped`/`hydrateScoped`
79
+ register the same finalizer explicitly, so the typed variant does not silently
80
+ depend on this auto-registration.
81
+
82
+ ## `@weftui/dom/server`
83
+
84
+ ### `renderToString`
85
+
86
+ ```ts
87
+ renderToString(node: Renderable): Effect<string, Error, AppRpcClientTag>
88
+ ```
89
+
90
+ Renders `node` to a complete HTML string. Use for static, non-hydrated output.
91
+
92
+ ### `renderToStringHydratable`
93
+
94
+ ```ts
95
+ renderToStringHydratable(node: Renderable): Effect<string, Error, AppRpcClientTag>
96
+ ```
97
+
98
+ Like `renderToString`, but embeds the hydration markers and inline boundary data
99
+ that `hydrate` needs on the client. Pair this with `hydrate`.
100
+
101
+ ### `renderToStream` / `renderToStreamHydratable`
102
+
103
+ ```ts
104
+ renderToStream(node: Renderable): Stream<string, Error, AppRpcClientTag>
105
+ renderToStreamHydratable(node: Renderable): Stream<string, Error, AppRpcClientTag>
106
+ ```
107
+
108
+ Streaming variants that emit HTML chunks as the tree resolves, so the browser can
109
+ start painting before the whole page is ready. The `Hydratable` variant includes the
110
+ hydration markers. These back streaming SSR and suspense.
111
+
112
+ ### `renderToHydratableShell`
113
+
114
+ ```ts
115
+ renderToHydratableShell(node: Renderable): Effect<HydratableShell, Error, R>
116
+ ```
117
+
118
+ Produces a `HydratableShell` — the document scaffold around the app — for servers
119
+ that assemble the response shell separately from the streamed body.
120
+
121
+ ### Suspense failure handling
122
+
123
+ `SuspenseFailureHandlerTag` is the service tag for a `SuspenseFailureHandler`, which
124
+ maps a failed suspense boundary to a `SuspenseFailureSubstitute` (fallback markup)
125
+ during streaming SSR.
126
+
127
+ ## Package root (`@weftui/dom`)
128
+
129
+ Re-exports the renderer error types:
130
+
131
+ - `HydrationMismatchError` — the client tree did not match the server markup.
132
+ - `UnsupportedNodeTypeError` — a node type the renderer cannot handle was encountered.
133
+ - `RenderError` — a general rendering failure.
134
+ - `StreamSubscriptionError` — a reactive stream backing the tree failed to subscribe.
135
+
136
+ ## See also
137
+
138
+ - [Render on the Server](https://weftui.dev/docs/how-to/render-on-the-server) — a narrative walkthrough of the server/client split
139
+ - [Provide Services](https://weftui.dev/docs/how-to/provide-services) — recipes for value layers, `mountScoped`, and `ManagedRuntime`
140
+ - [The Rendering Model](https://weftui.dev/docs/explanation/rendering-model) — hydrate-in-place and why there is no virtual DOM
141
+ - [Services and Context](https://weftui.dev/docs/explanation/services-and-context#layer-lifetime-at-the-mount) — why scoped layers need the mount to outlive initial render
142
+ - [`@weftui/core` reference](https://weftui.dev/docs/reference/core) · [`@weftui/router` reference](https://weftui.dev/docs/reference/router)