@weftui/dom 0.28.0 → 0.29.0

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.
Files changed (38) hide show
  1. package/README.md +25 -14
  2. package/dist/boundary-replay-BY4GyLot.js +1 -0
  3. package/dist/client/index.d.ts +5 -5
  4. package/dist/client/index.js +1 -1
  5. package/dist/index.d.ts +195 -1
  6. package/dist/index.js +1 -1
  7. package/dist/server/index.d.ts +6 -6
  8. package/dist/server/index.js +1 -1
  9. package/dist/shared-Dz0KM9ku.js +1 -0
  10. package/docs/explanation/boundaries-and-suspense.md +37 -18
  11. package/docs/explanation/combinator-api.md +14 -12
  12. package/docs/explanation/reactive-primitives.md +14 -14
  13. package/docs/explanation/rendering-model.md +20 -18
  14. package/docs/explanation/services-and-context.md +35 -21
  15. package/docs/how-to/add-routing.md +72 -48
  16. package/docs/how-to/author-components.md +40 -28
  17. package/docs/how-to/compose-behavior-and-markup.md +144 -0
  18. package/docs/how-to/handle-forms.md +6 -6
  19. package/docs/how-to/load-async-data.md +15 -13
  20. package/docs/how-to/load-data-with-rpc.md +30 -28
  21. package/docs/how-to/provide-services.md +20 -18
  22. package/docs/how-to/render-keyed-lists.md +10 -8
  23. package/docs/how-to/render-on-the-server.md +16 -12
  24. package/docs/how-to/show-navigation-progress.md +10 -8
  25. package/docs/how-to/split-routes-lazily.md +16 -14
  26. package/docs/how-to/style-reactively.md +13 -13
  27. package/docs/how-to/use-element-refs.md +10 -8
  28. package/docs/index.md +20 -18
  29. package/docs/reference/core.md +44 -40
  30. package/docs/reference/dom.md +274 -58
  31. package/docs/reference/router.md +69 -47
  32. package/docs/tutorial/01-your-first-app.md +7 -9
  33. package/docs/tutorial/02-reactivity.md +8 -6
  34. package/docs/tutorial/03-services-and-async.md +10 -6
  35. package/docs/tutorial/04-errors-and-server.md +14 -5
  36. package/package.json +3 -3
  37. package/dist/boundary-replay-BR26_puM.js +0 -1
  38. package/dist/data-uLmMpQMV.js +0 -1
@@ -2,14 +2,14 @@
2
2
  title: Style Reactively
3
3
  order: 10
4
4
  section: how-to
5
- description: Drive inline styles from streams a single property, or a whole style object so the DOM updates in place with CSS transitions.
5
+ description: Drive inline styles from streams (a single property, or a whole style object) so the DOM updates in place with CSS transitions.
6
6
  ---
7
7
 
8
8
  # Style Reactively
9
9
 
10
- **Goal:** animate or react to state in an element's inline style without re-rendering a single CSS property, or a whole style object, driven by a stream.
10
+ **Goal:** animate or react to state in an element's inline style without re-rendering. Drive a single CSS property, or a whole style object, from a stream.
11
11
 
12
- The `style` prop accepts the [`Source`](https://weftui.dev/docs/explanation/reactive-primitives) vocabulary at any level: a property value can be a stream, and you can spread a stream of style objects. CSS `transition` composes naturally, because the renderer mutates the existing node in place.
12
+ The `style` prop accepts the [`Source`](https://weftui.dev/docs/explanation/reactive-primitives) vocabulary at any level. A property value can be a stream, and you can spread a stream of style objects. CSS `transition` composes naturally, because the renderer mutates the existing node in place.
13
13
 
14
14
  ```typescript
15
15
  import { h } from "@weftui/core";
@@ -36,9 +36,9 @@ const AnimatedHue = () => {
36
36
 
37
37
  ## Three modes
38
38
 
39
- 1. **A single property as a stream** as above: one key's value is a `Stream`, the others are static strings. Each stream property is subscribed independently.
40
- 2. **A static object** an ordinary `style: { backgroundColor: "#667eea" }` with no streams; nothing updates.
41
- 3. **A whole style object as a stream** spread a stream that emits complete style objects, merged with static props:
39
+ 1. **A single property as a stream.** As above: one key's value is a `Stream`, the others are static strings. Each stream property is subscribed independently.
40
+ 2. **A static object.** An ordinary `style: { backgroundColor: "#667eea" }` with no streams; nothing updates.
41
+ 3. **A whole style object as a stream.** Spread a stream that emits complete style objects, merged with static props:
42
42
 
43
43
  ```typescript
44
44
  const pulse = Stream.make(1, 0.5).pipe(
@@ -49,15 +49,15 @@ const pulse = Stream.make(1, 0.5).pipe(
49
49
  h.div({ style: { ...pulse, transition: "opacity 0.4s ease-in-out" } }, "Pulse");
50
50
  ```
51
51
 
52
- Each emitted object is merged with the static properties on the element.
53
-
54
52
  ## Notes
55
53
 
56
- - **Property names are camelCase** (`backgroundColor`, `boxShadow`) the same keys as the DOM `style` object.
57
- - **CSS transitions just work.** Because a stream emission patches the DOM node directly (no re-render), the browser applies the `transition` as it would for any style mutation.
58
- - **Pace with `Schedule`.** `Stream.iterate`/`Stream.make` paced by `Stream.schedule(Schedule.spaced(…))` and looped with `Stream.forever` is the idiom for time-based style animation; combine with any Effect timing you like.
54
+ - **Property names are camelCase** (`backgroundColor`, `boxShadow`), the same keys as the DOM `style` object.
55
+ - **CSS transitions just work.** A stream emission patches the DOM node directly (no re-render), so the browser applies the `transition` as it would for any style mutation.
56
+ - **Pace with `Schedule`.** The idiom for time-based style animation: `Stream.iterate`/`Stream.make` paced by `Stream.schedule(Schedule.spaced(…))` and looped with `Stream.forever`. Combine with any Effect timing you like.
57
+ - **Classes have a reactive builder too.** `Props.cx` builds a class string from strings, falsy values, nested arrays, and `{ className: condition }` records, where a condition may be a stream. Merging two bags that both carry `class` concatenates them. See [Compose Behavior and Markup](https://weftui.dev/docs/how-to/compose-behavior-and-markup).
59
58
 
60
59
  ## See also
61
60
 
62
- - [Reactive Primitives](https://weftui.dev/docs/explanation/reactive-primitives) reactive style props and the `Source` vocabulary
63
- - [examples/reactive-styles](https://github.com/stefvw93/weft/tree/main/examples/reactive-styles) per-property and whole-object stream styles with CSS transitions
61
+ - [Reactive Primitives](https://weftui.dev/docs/explanation/reactive-primitives): reactive style props and the `Source` vocabulary
62
+ - [Compose Behavior and Markup](https://weftui.dev/docs/how-to/compose-behavior-and-markup): `Props.cx` and merging `class` across two prop bags
63
+ - [examples/reactive-styles](https://github.com/stefvw93/weft/tree/main/examples/reactive-styles): per-property and whole-object stream styles with CSS transitions
@@ -7,9 +7,9 @@ description: Capture a DOM element with the ref prop into a SubscriptionRef<Opti
7
7
 
8
8
  # Use Element Refs
9
9
 
10
- **Goal:** get a handle to a real DOM element to focus it, measure it, or call an imperative browser API on it.
10
+ **Goal:** get a handle to a real DOM element, to focus it, measure it, or call an imperative browser API on it.
11
11
 
12
- Declare a `SubscriptionRef<Option<HTMLElement>>`, attach it with the `ref` prop, and either **react** to the element appearing (a scoped observer on `SubscriptionRef.changes(ref)`) or **read** it later inside a handler.
12
+ Declare a `SubscriptionRef<Option<HTMLElement>>` and attach it with the `ref` prop. Then either **react** to the element appearing (a scoped observer on `SubscriptionRef.changes(ref)`) or **read** it later inside a handler.
13
13
 
14
14
  ```typescript
15
15
  import { h } from "@weftui/core";
@@ -34,9 +34,9 @@ const AutoFocusInput = () =>
34
34
 
35
35
  ## How it works
36
36
 
37
- - **The `ref` prop** takes a `SubscriptionRef<Option<T>>`. The renderer sets it to `Option.some(element)` **once**, when the element is created so the ref is an `Option`: `None` until mount, `Some(el)` after.
38
- - **React to mount** by observing `SubscriptionRef.changes(ref)`: `Stream.filter(Option.isSome)` waits for the element, `Stream.take(1)` takes just the first appearance, and `Stream.runForEach` does the imperative work. This is the equivalent of a mount effect.
39
- - **Use `Effect.forkScoped`, not `Effect.forkChild`.** `forkScoped` ties the observer fiber to the component's **instance scope** (the ambient `Scope` the renderer provides), so it lives as long as the component is mounted. A bare `Effect.forkChild` binds to the transient component-body fiber and is interrupted the instant the generator returns the observer would never fire.
37
+ - **The `ref` prop** takes a `SubscriptionRef<Option<T>>`. The renderer sets it to `Option.some(element)` **once**, when the element is created. The ref is therefore an `Option`: `None` until mount, `Some(el)` after.
38
+ - **React to mount** by observing `SubscriptionRef.changes(ref)`. `Stream.filter(Option.isSome)` waits for the element, `Stream.take(1)` takes just the first appearance, and `Stream.runForEach` does the imperative work. This is the equivalent of a mount effect.
39
+ - **Use `Effect.forkScoped`, not `Effect.forkChild`.** `forkScoped` ties the observer fiber to the component's **instance scope** (the ambient `Scope` the renderer provides). It lives as long as the component is mounted. A bare `Effect.forkChild` binds to the transient component-body fiber and is interrupted the instant the generator returns, so the observer would never fire.
40
40
 
41
41
  ## Read a ref imperatively
42
42
 
@@ -54,10 +54,12 @@ const scroll = () =>
54
54
 
55
55
  - A plain `Ref` suffices if you **only** read the element imperatively; use `SubscriptionRef` when you need to **react** to it becoming available.
56
56
  - Refs are set once at element creation and are not cleared on unmount.
57
+ - **Several refs can share one element.** `ref` also accepts an array, and every entry receives the element: `h.div({ ref: [measure, focus] })`. `Props.merge` produces such an array when both bags carry a `ref`, so a shared behavior's ref and your own can coexist. See [Compose Behavior and Markup](https://weftui.dev/docs/how-to/compose-behavior-and-markup).
57
58
  - Coming from React: `SubscriptionRef.make<Option<T>>(Option.none())` ↔ `useRef<T>(null)`; the `Stream.filter(Option.isSome)` observer ↔ a `useEffect` mount guard.
58
59
 
59
60
  ## See also
60
61
 
61
- - [Reactive Primitives](https://weftui.dev/docs/explanation/reactive-primitives) `SubscriptionRef` and `SubscriptionRef.changes`
62
- - [Author Components](https://weftui.dev/docs/how-to/author-components) instance scope and `Effect.forkScoped`
63
- - [examples/element-ref](https://github.com/stefvw93/weft/tree/main/examples/element-ref) auto-focus, element measurement, and imperative scroll via refs
62
+ - [Reactive Primitives](https://weftui.dev/docs/explanation/reactive-primitives): `SubscriptionRef` and `SubscriptionRef.changes`
63
+ - [Author Components](https://weftui.dev/docs/how-to/author-components): instance scope and `Effect.forkScoped`
64
+ - [Compose Behavior and Markup](https://weftui.dev/docs/how-to/compose-behavior-and-markup): merging a shared behavior's `ref` with your own
65
+ - [examples/element-ref](https://github.com/stefvw93/weft/tree/main/examples/element-ref): auto-focus, element measurement, and imperative scroll via refs
package/docs/index.md CHANGED
@@ -2,40 +2,42 @@
2
2
 
3
3
  **Reactive UI, woven from Effect.**
4
4
 
5
- Weft is an Effect-native reactive DOM library in the browser and on the server. `Node<E, R>` is `Effect.Effect<ElementDescriptor, E, R>`: every element is an Effect, so error and requirement channels accumulate through the tree, all Effect combinators apply to nodes directly, and services flow from mount through the whole app. Streams drive every update — there is no virtual DOM — and the same tree renders to HTML on the server and `hydrate()`s in place on the client, flash-free. No JSX.
5
+ Weft is an Effect-native reactive DOM library, in the browser and on the server. `Node<E, R>` is `Effect.Effect<ElementDescriptor, E, R>`: every element is an Effect. Error and requirement channels accumulate through the tree, all Effect combinators apply to nodes directly, and services flow from mount through the whole app.
6
+
7
+ Streams drive every update; there is no virtual DOM. The same tree renders to HTML on the server and `hydrate()`s in place on the client, flash-free. No JSX.
6
8
 
7
9
  The docs follow the [Diátaxis](https://diataxis.fr) model. Pick your entry point by what you are trying to do:
8
10
 
9
11
  ## Start here
10
12
 
11
- **[→ Tutorial](https://weftui.dev/docs/tutorial/01-your-first-app)** a four-step guided path from a static component to a server-rendered, error-handled app. Start here if you are new to Weft:
13
+ **[→ Tutorial](https://weftui.dev/docs/tutorial/01-your-first-app)**: a four-step guided path from a static component to a server-rendered, error-handled app. Start here if you are new to Weft:
12
14
 
13
- 1. [Your First App](https://weftui.dev/docs/tutorial/01-your-first-app) `h` and `WeftApp`
14
- 2. [Reactivity](https://weftui.dev/docs/tutorial/02-reactivity) `SubscriptionRef` and streams
15
- 3. [Services and Async](https://weftui.dev/docs/tutorial/03-services-and-async) handlers, services, async loading
16
- 4. [Errors and Server Rendering](https://weftui.dev/docs/tutorial/04-errors-and-server) boundaries and SSR
15
+ 1. [Your First App](https://weftui.dev/docs/tutorial/01-your-first-app): `h` and `WeftApp`
16
+ 2. [Reactivity](https://weftui.dev/docs/tutorial/02-reactivity): `SubscriptionRef` and streams
17
+ 3. [Services and Async](https://weftui.dev/docs/tutorial/03-services-and-async): handlers, services, async loading
18
+ 4. [Errors and Server Rendering](https://weftui.dev/docs/tutorial/04-errors-and-server): boundaries and SSR
17
19
 
18
20
  ## The four quadrants
19
21
 
20
- | | |
21
- | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
22
- | **[Tutorial](https://weftui.dev/docs/tutorial/01-your-first-app)** | Learning-oriented. One guided path, start to finish. |
23
- | **[How-to guides](https://weftui.dev/docs/how-to/author-components)** | Task-oriented. Author components, render on the server, load data with rpc, add routing plus recipes for forms, async data, keyed lists, reactive styles, refs, and lazy routing. |
24
- | **[Explanation](https://weftui.dev/docs/explanation/rendering-model)** | Understanding-oriented. The rendering model, the combinator API, reactive primitives, boundaries, and services & context. |
25
- | **[Reference](https://weftui.dev/docs/reference/core)** | Information-oriented. Full API: [`@weftui/core`](https://weftui.dev/docs/reference/core), [`@weftui/dom`](https://weftui.dev/docs/reference/dom), [`@weftui/router`](https://weftui.dev/docs/reference/router). |
22
+ | | |
23
+ | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
24
+ | **[Tutorial](https://weftui.dev/docs/tutorial/01-your-first-app)** | Learning-oriented. One guided path, start to finish. |
25
+ | **[How-to guides](https://weftui.dev/docs/how-to/author-components)** | Task-oriented. Author components, render on the server, load data with rpc, add routing. Plus recipes for forms, async data, keyed lists, reactive styles, refs, and lazy routing. |
26
+ | **[Explanation](https://weftui.dev/docs/explanation/rendering-model)** | Understanding-oriented. The rendering model, the combinator API, reactive primitives, boundaries, and services & context. |
27
+ | **[Reference](https://weftui.dev/docs/reference/core)** | Information-oriented. Full API: [`@weftui/core`](https://weftui.dev/docs/reference/core), [`@weftui/dom`](https://weftui.dev/docs/reference/dom), [`@weftui/router`](https://weftui.dev/docs/reference/router). |
26
28
 
27
- New to the model itself? Read [The Rendering Model](https://weftui.dev/docs/explanation/rendering-model) why there is no virtual DOM, and what "streams are the weft" means.
29
+ New to the model itself? Read [The Rendering Model](https://weftui.dev/docs/explanation/rendering-model): why there is no virtual DOM, and what "streams are the weft" means.
28
30
 
29
31
  ## Packages
30
32
 
31
33
  Three published packages make up Weft's public API, plus one build-time plugin:
32
34
 
33
- - **`@weftui/core`** element builders (`h`), components, sources/streams, and boundaries. Start here.
34
- - **`@weftui/dom`** the renderer: `./client` (`WeftApp.mount`/`WeftApp.hydrate`) and `./server` (`renderToString*`).
35
- - **`@weftui/router`** universal nested routing, `Router.lazy`, and the rpc seam.
36
- - **`@weftui/vite`** a build-time Vite plugin (tooling, not a runtime API).
35
+ - **`@weftui/core`**: element builders (`h`), components, sources/streams, and boundaries. Start here.
36
+ - **`@weftui/dom`**: the renderer, with `./client` (`WeftApp.mount`/`WeftApp.hydrate`) and `./server` (`renderToString*`) entry points.
37
+ - **`@weftui/router`**: universal nested routing, `Router.lazy`, and the rpc seam.
38
+ - **`@weftui/vite`**: a build-time Vite plugin (tooling, not a runtime API).
37
39
 
38
- `@weftui/base` is an internal, currently-empty stub it has no public primitives; ignore it.
40
+ `@weftui/base` is an internal, currently-empty stub with no public primitives. Ignore it.
39
41
 
40
42
  ## Examples
41
43
 
@@ -2,7 +2,7 @@
2
2
  title: "@weftui/core"
3
3
  order: 1
4
4
  section: reference
5
- description: Full API surface for @weftui/core element builders, components, sources, streams, and boundaries.
5
+ description: "Full API surface for @weftui/core: element builders, components, sources, streams, and boundaries."
6
6
  ---
7
7
 
8
8
  # @weftui/core API Reference
@@ -52,7 +52,7 @@ Use when a component needs to return multiple sibling elements.
52
52
 
53
53
  ### `Component`
54
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.
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`. Reactive prop values and reactive children therefore contribute their `E`/`R` at the call site.
56
56
 
57
57
  ```typescript
58
58
  import { Component } from "@weftui/core";
@@ -69,7 +69,7 @@ Component.make<BaseProps, C>(
69
69
  ): /* same call signature as above */;
70
70
  ```
71
71
 
72
- **`Children`** the optional `children` argument may be either form:
72
+ **`Children`**: the optional `children` argument may be either form:
73
73
 
74
74
  ```typescript
75
75
  type Component.Children<Input = never> =
@@ -79,7 +79,7 @@ type Component.Children<Input = never> =
79
79
 
80
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
81
 
82
- **Example `Component.gen`**:
82
+ **Example: `Component.gen`**
83
83
 
84
84
  ```typescript
85
85
  interface TextFieldProps {
@@ -96,7 +96,7 @@ const TextField = Component.gen(function* (props: TextFieldProps) {
96
96
  });
97
97
  ```
98
98
 
99
- **Example `Component.make` with function-children**:
99
+ **Example: `Component.make` with function-children**
100
100
 
101
101
  ```typescript
102
102
  const Labeled = Component.make(
@@ -108,17 +108,17 @@ Labeled({ label: "Name" }, (label) => [h.label(label), h.input()]);
108
108
  ```
109
109
 
110
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
111
+ > rather than mapping items by hand. It reconciles by key across emissions instead of
112
112
  > rebuilding the region.
113
113
 
114
114
  ### `Boundary` namespace
115
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.
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
117
 
118
118
  **What is caught:**
119
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
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
122
 
123
123
  **What is NOT caught:** event handler errors (they run in detached fibers outside the render path).
124
124
 
@@ -144,7 +144,9 @@ Boundary.suspend(
144
144
 
145
145
  #### `Boundary.rpc`
146
146
 
147
- A universal server/client render boundary backed by one `Rpc` from the app's merged `RpcGroup` ([`effect/unstable/rpc`](https://github.com/Effect-TS/effect)). 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.
147
+ A universal server/client render boundary backed by one `Rpc` from the app's merged `RpcGroup` ([`effect/unstable/rpc`](https://github.com/Effect-TS/effect)). 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.
148
+
149
+ Unlike the catch variants it takes a `render` function, not a children array. That `render` receives a reactive [`Resource`](#resourcea), not a bare value.
148
150
 
149
151
  ```typescript
150
152
  Boundary.rpc<R extends Rpc.Any, C extends Node<any, any>>(
@@ -157,27 +159,27 @@ Boundary.rpc<R extends Rpc.Any, C extends Node<any, any>>(
157
159
 
158
160
  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
161
 
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).
162
+ - **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. It then renders `render(seededResource)` to HTML in place.
163
+ - **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).
164
+ - **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
165
  - **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
166
 
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).
167
+ **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
168
 
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.
169
+ **Typed-failure replay:** a resolved rpc **error** on the SSR pass is `errorSchema`-encoded and relocated to the nearest enclosing failure `Boundary`. It is 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
170
 
169
171
  > **Not yet covered:** streamed success (`Rpc.make(..., { stream: true })`) and mutations are on the roadmap, not this pass.
170
172
 
171
173
  ##### `Resource<A>`
172
174
 
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.
175
+ The reactive handle `render` receives (`A = Rpc.Success<R>`). After hydrate the region is live: `value` is seeded with the SSR payload. The client can `refetch` the same data on demand, patching the rendered subtree in place.
174
176
 
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`. |
177
+ | Field | Type | Meaning |
178
+ | --------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
179
+ | `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. |
180
+ | `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. |
181
+ | `pending` | `Subscribable.Subscribable<boolean>` | `true` while a refetch is in flight (`false` on the server / before any refetch). |
182
+ | `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
183
 
182
184
  ##### `RpcOptions`
183
185
 
@@ -206,7 +208,9 @@ class AppRpcClientTag extends Context.Service<AppRpcClientTag, AppRpcClient>()(
206
208
  ) {}
207
209
  ```
208
210
 
209
- 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/unstable/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
+ 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/unstable/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.
212
+
213
+ `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).
210
214
 
211
215
  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).
212
216
 
@@ -267,7 +271,9 @@ Boundary.catchTags<C, Handlers>(
267
271
 
268
272
  #### `Boundary.catchFilter`
269
273
 
270
- Conditionally catches using a `Filter`, run on each typed failure: a `Result.succeed` (pass) recovers via `fallback`, receiving the possibly-narrowed pass value; a `Result.fail` re-raises the error (its `Fail` channel `X` is preserved in the output `E`, since the boundary may not handle any given error). Takes the `Filter` and `fallback` as **positional** arguments — no wrapping props object. Mirrors Effect 4's `Effect.catchFilter` (renamed from `catchSome`, which took an `Option`-returning function in v3).
274
+ Conditionally catches using a `Filter`, run on each typed failure. A `Result.succeed` (pass) recovers via `fallback`, receiving the possibly-narrowed pass value. A `Result.fail` re-raises the error; its `Fail` channel `X` is preserved in the output `E`, since the boundary may not handle any given error.
275
+
276
+ Takes the `Filter` and `fallback` as **positional** arguments (no wrapping props object). Mirrors Effect 4's `Effect.catchFilter` (renamed from `catchSome`, which took an `Option`-returning function in v3).
271
277
 
272
278
  ```typescript
273
279
  Boundary.catchFilter<C, EB, X, FE, FR>(
@@ -288,8 +294,6 @@ Boundary.catchFilter(
288
294
  );
289
295
  ```
290
296
 
291
- The children's `E` is narrowed to the filter's `Fail` channel `X` in the output, because the boundary may or may not handle any given error.
292
-
293
297
  #### `Boundary.catchIf`
294
298
 
295
299
  A predicate gates the fallback. `false` re-raises.
@@ -308,7 +312,7 @@ Boundary.catchIf<C, FE, FR>(
308
312
 
309
313
  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.
310
314
 
311
- Inner boundaries shadow outer ones for their subtree the innermost boundary is always tried first.
315
+ Inner boundaries shadow outer ones for their subtree: the innermost boundary is always tried first.
312
316
 
313
317
  ```typescript
314
318
  // Inner catches FooError; BarError propagates to outer
@@ -323,7 +327,7 @@ Boundary.catch({ fallback: (e) => h.div(`Outer: ${e.message}`) }, [
323
327
 
324
328
  ## ServerTag
325
329
 
326
- A `Context.Service` key whose identifier is branded server-only. Use it exactly like `Context.Service` 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.
330
+ A `Context.Service` key whose identifier is branded server-only. Use it exactly like `Context.Service` 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.
327
331
 
328
332
  ```typescript
329
333
  import { ServerTag } from "@weftui/core";
@@ -336,8 +340,8 @@ class Database extends ServerTag("Database")<
336
340
  ```
337
341
 
338
342
  - The server-only brand rides along in the requirement channel `R` of any effect that uses the tag.
339
- - 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.
340
- - 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.
343
+ - 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.
344
+ - 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`). The failure surfaces at the `hydrate` call site, not silently at runtime.
341
345
 
342
346
  `ServerOnly`, `ServerOnlyLeak`, and `AssertNoServerOnly<R>` are exported alongside `ServerTag` for advanced typing; most code only needs `ServerTag` itself.
343
347
 
@@ -347,10 +351,10 @@ class Database extends ServerTag("Database")<
347
351
 
348
352
  ### `List` namespace
349
353
 
350
- 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.
354
+ The keyed-list combinator. It is the opt-in alternative to wholesale child rebuilds: items are rendered **once per key** and reconciled across emissions. Reordering, inserting, or removing items therefore reuses and moves existing DOM rather than rebuilding the region.
351
355
 
352
356
  > **Note:** This exported `List` namespace is the built-in, key-reconciling way to
353
- > render collections prefer it over hand-rolling a component that maps items into
357
+ > render collections. Prefer it over hand-rolling a component that maps items into
354
358
  > elements.
355
359
 
356
360
  ```typescript
@@ -372,7 +376,7 @@ List.each<S extends Source.Source<Iterable<any>, any, any>, CE, CR, K>(
372
376
 
373
377
  `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.
374
378
 
375
- > **⚠️ 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`).
379
+ > **⚠️ 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`).
376
380
 
377
381
  **`List.Options<S, K>`**
378
382
 
@@ -383,8 +387,8 @@ interface List.Options<S, K> {
383
387
  }
384
388
  ```
385
389
 
386
- - **`of`** the list source. Each emission is materialized to an array to fix order, then reconciled by key.
387
- - **`by`** projects each item to its reconciliation key. Omitted ⇒ the item itself is the key (structural for `Data`, by reference otherwise).
390
+ - **`of`**: the list source. Each emission is materialized to an array to fix order, then reconciled by key.
391
+ - **`by`**: projects each item to its reconciliation key. Omitted ⇒ the item itself is the key (structural for `Data`, by reference otherwise).
388
392
 
389
393
  #### `List.Error<N>` and `List.Context<N>`
390
394
 
@@ -435,7 +439,7 @@ Normalization rules:
435
439
  - **`Effect`** → memoized via `Effect.cached`; `changes` emits the resolved value once
436
440
  - **`Stream`** → forks a scoped pump fiber that drains into a `SubscriptionRef`; `get` awaits the first emission
437
441
 
438
- The pump fiber is tied to the enclosing scope via `Effect.forkScoped` it terminates when the scope closes.
442
+ The pump fiber is tied to the enclosing scope via `Effect.forkScoped`. It terminates when the scope closes.
439
443
 
440
444
  ### `NoPropValue`
441
445
 
@@ -458,7 +462,7 @@ type PropsE<P> = { [K in keyof P]: P[K] extends Stream.Stream<any, infer E, any>
458
462
  type PropsR<P> = { [K in keyof P]: P[K] extends Stream.Stream<any, any, infer R> ? R : ... }[keyof P]
459
463
  ```
460
464
 
461
- 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.
465
+ These are used internally by `h` and `Component` to accumulate channels from props. Reference them directly only when building utilities over the combinator API.
462
466
 
463
467
  ---
464
468
 
@@ -466,7 +470,7 @@ These are used internally by `h` and `Component` to accumulate channels from pro
466
470
 
467
471
  ### `FRAGMENT`
468
472
 
469
- Internal brand used to mark fragment nodes. Not intended for direct use use `h.fragment` instead.
473
+ Internal brand used to mark fragment nodes. Not intended for direct use; use `h.fragment` instead.
470
474
 
471
475
  ---
472
476
 
@@ -502,6 +506,6 @@ isSubscribable(value: unknown): value is Subscribable<unknown, unknown, unknown>
502
506
 
503
507
  ## See also
504
508
 
505
- - [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
506
- - [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
509
+ - [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
510
+ - [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
507
511
  - [`@weftui/dom` reference](https://weftui.dev/docs/reference/dom) · [`@weftui/router` reference](https://weftui.dev/docs/reference/router)