@weftui/core 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.
- package/README.md +8 -8
- package/dist/{index-Bsh2WLtx.d.ts → index-4cTlhojA.d.ts} +45 -31
- package/dist/index.d.ts +68 -119
- package/dist/types/index.d.ts +1 -1
- package/docs/explanation/boundaries-and-suspense.md +37 -18
- package/docs/explanation/combinator-api.md +14 -12
- package/docs/explanation/reactive-primitives.md +14 -14
- package/docs/explanation/rendering-model.md +20 -18
- package/docs/explanation/services-and-context.md +35 -21
- package/docs/how-to/add-routing.md +72 -48
- package/docs/how-to/author-components.md +40 -28
- package/docs/how-to/compose-behavior-and-markup.md +144 -0
- package/docs/how-to/handle-forms.md +6 -6
- package/docs/how-to/load-async-data.md +15 -13
- package/docs/how-to/load-data-with-rpc.md +30 -28
- package/docs/how-to/provide-services.md +20 -18
- package/docs/how-to/render-keyed-lists.md +10 -8
- package/docs/how-to/render-on-the-server.md +16 -12
- package/docs/how-to/show-navigation-progress.md +10 -8
- package/docs/how-to/split-routes-lazily.md +16 -14
- package/docs/how-to/style-reactively.md +13 -13
- package/docs/how-to/use-element-refs.md +10 -8
- package/docs/index.md +20 -18
- package/docs/reference/core.md +44 -40
- package/docs/reference/dom.md +274 -58
- package/docs/reference/router.md +69 -47
- package/docs/tutorial/01-your-first-app.md +7 -9
- package/docs/tutorial/02-reactivity.md +8 -6
- package/docs/tutorial/03-services-and-async.md +10 -6
- package/docs/tutorial/04-errors-and-server.md +14 -5
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { D as Subscribable, E as Source, O as index_d_exports, T as NoPropValue, a as SVGElements, r as Renderable, s as HTMLElements, t as ElementDescriptor } from "./index-
|
|
1
|
+
import { D as Subscribable, E as Source, O as index_d_exports, T as NoPropValue, a as SVGElements, r as Renderable, s as HTMLElements, t as ElementDescriptor } from "./index-4cTlhojA.js";
|
|
2
2
|
import { Cause, Context, Effect, Filter, Option, Stream } from "effect";
|
|
3
3
|
import { Rpc } from "effect/unstable/rpc";
|
|
4
4
|
//#region src/combinator/types.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* Widens a single prop value type so that Stream/Effect/Subscribable variants
|
|
7
|
-
* accept any E and R
|
|
7
|
+
* accept any E and R, not just `never`. Static values (string, number, etc.)
|
|
8
8
|
* are left unchanged. TypeScript distributes this over union types.
|
|
9
9
|
*/
|
|
10
10
|
type OpenPropSource<T> = T extends Stream.Stream<infer A, any, any> ? Stream.Stream<A, any, any> : T extends Effect.Effect<infer A, any, any> ? Effect.Effect<A, any, any> : T extends Subscribable<infer A, any, any> ? Subscribable<A, any, any> : T;
|
|
11
11
|
/**
|
|
12
12
|
* A node in the combinator tree.
|
|
13
|
-
* IS an Effect
|
|
13
|
+
* IS an Effect: `yield*`, `Effect.gen`, and `pipe` all work natively. Resolves
|
|
14
14
|
* to an {@link ElementDescriptor}.
|
|
15
15
|
*
|
|
16
16
|
* Declared as an interface (rather than a bare alias) so it can merge with the
|
|
@@ -32,17 +32,17 @@ declare namespace Node {
|
|
|
32
32
|
/** Extract the requirement channel `R` from a {@link Node} (a static node ⇒ `never`). */
|
|
33
33
|
type Context<N> = [N] extends [Effect.Effect<any, any, infer R>] ? R : never;
|
|
34
34
|
}
|
|
35
|
-
/** Extract E from a props object
|
|
35
|
+
/** Extract E from a props object: Stream/Effect/Subscribable prop values and Effect-returning event handlers contribute their E channel. */
|
|
36
36
|
type PropsE<P> = { [K in keyof P]: P[K] extends Stream.Stream<any, infer E, any> ? E : P[K] extends Effect.Effect<any, infer E, any> ? E : P[K] extends Subscribable<any, infer E, any> ? E : P[K] extends ((...args: any[]) => infer Ret) ? Ret extends Effect.Effect<any, infer E, any> ? E : never : never; }[keyof P];
|
|
37
|
-
/** Extract R from a props object
|
|
37
|
+
/** Extract R from a props object: Stream/Effect/Subscribable prop values and Effect-returning event handlers contribute their R channel. */
|
|
38
38
|
type PropsR<P> = { [K in keyof P]: P[K] extends Stream.Stream<any, any, infer R> ? R : P[K] extends Effect.Effect<any, any, infer R> ? R : P[K] extends Subscribable<any, any, infer R> ? R : P[K] extends ((...args: any[]) => infer Ret) ? Ret extends Effect.Effect<any, any, infer R> ? R : never : never; }[keyof P];
|
|
39
|
-
/** Extract E from a children array
|
|
39
|
+
/** Extract E from a children array: Node (Effect) and Stream children contribute their E. */
|
|
40
40
|
type ChildrenE<T extends readonly Renderable[]> = [T[number]] extends [never] ? never : { [K in keyof T]: T[K] extends Effect.Effect<any, infer E, any> ? E : T[K] extends Stream.Stream<any, infer E, any> ? E : never; }[number];
|
|
41
|
-
/** Extract R from a children array
|
|
41
|
+
/** Extract R from a children array: Node (Effect) and Stream children contribute their R. */
|
|
42
42
|
type ChildrenR<T extends readonly Renderable[]> = [T[number]] extends [never] ? never : { [K in keyof T]: T[K] extends Effect.Effect<any, any, infer R> ? R : T[K] extends Stream.Stream<any, any, infer R> ? R : never; }[number];
|
|
43
43
|
/**
|
|
44
44
|
* Strip `children` from HTML prop types and widen all Source prop types to
|
|
45
|
-
* allow any E/R
|
|
45
|
+
* allow any E/R, so callers can pass `Stream<T, E, R>` with real requirements.
|
|
46
46
|
*/
|
|
47
47
|
type CombinatorialProps<P> = { [K in keyof Omit<P, "children">]: OpenPropSource<Omit<P, "children">[K]>; };
|
|
48
48
|
declare namespace boundary_impl_d_exports {
|
|
@@ -76,8 +76,8 @@ type CatchTagsE<C extends readonly Renderable[], Tags extends string> = Exclude<
|
|
|
76
76
|
* variant wraps a subtree and shows a fallback in response to an event.
|
|
77
77
|
*
|
|
78
78
|
* - **Failure boundaries** (`catch`, `catchCause`, `catchTag`,
|
|
79
|
-
* `catchTags`, `catchFilter`, `catchIf`) intercept rendering-path errors
|
|
80
|
-
* construction-time errors and post-mount stream failures
|
|
79
|
+
* `catchTags`, `catchFilter`, `catchIf`) intercept rendering-path errors
|
|
80
|
+
* (construction-time errors and post-mount stream failures), mirroring
|
|
81
81
|
* Effect's `catch*` combinators.
|
|
82
82
|
* - **Suspense boundary** (`suspend`) shows a fallback while async children are
|
|
83
83
|
* pending, then swaps to the resolved children once all have settled.
|
|
@@ -90,7 +90,7 @@ type CatchTagsE<C extends readonly Renderable[], Tags extends string> = Exclude<
|
|
|
90
90
|
* ```ts
|
|
91
91
|
* import { Boundary, h } from "@weftui/core";
|
|
92
92
|
*
|
|
93
|
-
* // Failure boundary wrapping a suspense boundary
|
|
93
|
+
* // Failure boundary wrapping a suspense boundary, the common pairing:
|
|
94
94
|
* Boundary.catch({ fallback: (e) => h.div({}, e.message) }, [
|
|
95
95
|
* Boundary.suspend({ fallback: h.div({}, "Loading…") }, [AsyncCard()]),
|
|
96
96
|
* ])
|
|
@@ -109,7 +109,7 @@ interface FailureProps {
|
|
|
109
109
|
readonly match: (cause: Cause.Cause<unknown>) => Node<unknown, unknown> | null;
|
|
110
110
|
}
|
|
111
111
|
/**
|
|
112
|
-
* Props for the {@link suspend} boundary
|
|
112
|
+
* Props for the {@link suspend} boundary, read by renderers to access
|
|
113
113
|
* `fallback` and `children` from the node descriptor.
|
|
114
114
|
*/
|
|
115
115
|
interface SuspenseProps {
|
|
@@ -185,7 +185,7 @@ declare function catchTags<C extends readonly Renderable[], Handlers extends { r
|
|
|
185
185
|
*/
|
|
186
186
|
declare function catchFilter<C extends readonly Renderable[], EB, X, FE = never, FR = never>(filter: Filter.Filter<ChildrenE<C>, EB, X>, fallback: (matched: EB) => Node<FE, FR>, children: C): Node<X | FE, ChildrenR<C> | FR>;
|
|
187
187
|
/**
|
|
188
|
-
* Conditionally catch
|
|
188
|
+
* Conditionally catch: a predicate gates the fallback. If the predicate
|
|
189
189
|
* returns `false`, the error is re-raised. The children's `E` is preserved
|
|
190
190
|
* in the output since the boundary may not handle any given error.
|
|
191
191
|
*/
|
|
@@ -196,19 +196,10 @@ declare function catchIf<C extends readonly Renderable[], FE = never, FR = never
|
|
|
196
196
|
/**
|
|
197
197
|
* Creates a suspense boundary node.
|
|
198
198
|
*
|
|
199
|
-
* Shows `fallback` while async children are pending (have not yet emitted
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
* The renderer (`@weftui/dom`) identifies the boundary via its
|
|
204
|
-
* {@link SUSPENSE_BOUNDARY} type tag.
|
|
205
|
-
*
|
|
206
|
-
* @example
|
|
207
|
-
* ```ts
|
|
208
|
-
* import { Boundary, h } from "@weftui/core";
|
|
209
|
-
*
|
|
210
|
-
* Boundary.suspend({ fallback: h.div({}, "Loading…") }, [AsyncCard(), AsyncSidebar()])
|
|
211
|
-
* ```
|
|
199
|
+
* Shows `fallback` while async children are pending (have not yet emitted their
|
|
200
|
+
* first value), then atomically swaps to the resolved children once **all**
|
|
201
|
+
* pending children have settled. The renderer (`@weftui/dom`) identifies the
|
|
202
|
+
* boundary via its {@link SUSPENSE_BOUNDARY} type tag.
|
|
212
203
|
*/
|
|
213
204
|
declare function suspend<C extends readonly Renderable[]>(props: SuspenseProps, children: C): Node<ChildrenE<C>, ChildrenR<C>>;
|
|
214
205
|
/**
|
|
@@ -220,7 +211,7 @@ declare function suspend<C extends readonly Renderable[]>(props: SuspenseProps,
|
|
|
220
211
|
*
|
|
221
212
|
* On the **server** and on the **first client paint after hydrate** `value`
|
|
222
213
|
* emits the SSR `data` first (await-first), so SSR HTML and the adopted DOM are
|
|
223
|
-
* byte-identical
|
|
214
|
+
* byte-identical, with no fallback flash.
|
|
224
215
|
*
|
|
225
216
|
* @typeParam A - The loaded data shape.
|
|
226
217
|
*/
|
|
@@ -240,7 +231,7 @@ interface Resource<A> {
|
|
|
240
231
|
readonly pending: Subscribable<boolean>;
|
|
241
232
|
/**
|
|
242
233
|
* `Some` with the last refetch error, else `None`. A failed refetch leaves
|
|
243
|
-
* the previous `value` intact (stale-on-error)
|
|
234
|
+
* the previous `value` intact (stale-on-error). It does **not** unmount the
|
|
244
235
|
* subtree or raise into an enclosing failure `Boundary`.
|
|
245
236
|
*/
|
|
246
237
|
readonly error: Subscribable<Option.Option<unknown>>;
|
|
@@ -262,7 +253,7 @@ interface RpcOptions {
|
|
|
262
253
|
*
|
|
263
254
|
* The boundary is a thin consumer of one `Rpc` from the application's merged
|
|
264
255
|
* `RpcGroup`. Its data source is the ambient {@link AppRpcClient}: there is no
|
|
265
|
-
* co-located `load`, no `provide`, no per-boundary `id`/registry
|
|
256
|
+
* co-located `load`, no `provide`, no per-boundary `id`/registry. The rpc
|
|
266
257
|
* **tag** is the stable identity and the rpc **payload schema** is the typed
|
|
267
258
|
* input. The handler lives in the server-only rpc Layer, so nothing here needs a
|
|
268
259
|
* bundler prune.
|
|
@@ -272,7 +263,7 @@ interface RpcOptions {
|
|
|
272
263
|
* the rpc's `successSchema` inline as a `<script type="application/json">`
|
|
273
264
|
* payload, and renders `render(seededResource)` to HTML in place.
|
|
274
265
|
* - **Hydrate**: reads the inline payload at the cursor, decodes via
|
|
275
|
-
* `successSchema`, seeds the {@link Resource}, and adopts the DOM
|
|
266
|
+
* `successSchema`, seeds the {@link Resource}, and adopts the DOM, replaying
|
|
276
267
|
* the server result, never re-calling the rpc.
|
|
277
268
|
* - **Refetch**: `Resource.refetch` calls `AppRpcClient.call(tag, payload())`
|
|
278
269
|
* over the network and patches the subtree in place (stale-on-error).
|
|
@@ -281,7 +272,7 @@ interface RpcOptions {
|
|
|
281
272
|
* `render(resource)` once it resolves.
|
|
282
273
|
*
|
|
283
274
|
* `payload` is a thunk so a fresh payload is produced per call (SSR, refetch,
|
|
284
|
-
* mount)
|
|
275
|
+
* mount). Its return type is the rpc's decoded payload. The renderer identifies
|
|
285
276
|
* the boundary via its {@link SERVER_BOUNDARY} type tag.
|
|
286
277
|
*
|
|
287
278
|
* @example
|
|
@@ -308,9 +299,9 @@ declare function rpc<R extends Rpc.Any, C extends Node<any, any>>(rpc: R, payloa
|
|
|
308
299
|
/**
|
|
309
300
|
* Ambient, package-neutral seam for resolving a {@link Boundary.rpc} boundary's
|
|
310
301
|
* data through the application's merged `RpcGroup`. The DOM renderer
|
|
311
|
-
* (`@weftui/dom`) must resolve a boundary
|
|
312
|
-
*
|
|
313
|
-
*
|
|
302
|
+
* (`@weftui/dom`) must resolve a boundary **without** importing
|
|
303
|
+
* `effect/unstable/rpc` or `@weftui/router`, on the server (SSR), during a
|
|
304
|
+
* client refetch, and on a client-first SPA mount. So the rpc caller is injected as a
|
|
314
305
|
* service: `@weftui/router` provides it (a network `RpcClient` on the browser,
|
|
315
306
|
* an in-process client over the handler layer on the server), and the renderer
|
|
316
307
|
* reads it from ambient context, treating `Option.none` (no router/rpc present)
|
|
@@ -334,8 +325,8 @@ interface AppRpcClient {
|
|
|
334
325
|
}
|
|
335
326
|
declare const AppRpcClientTag_base: Context.ServiceClass<AppRpcClientTag, "@weftui/core/AppRpcClient", AppRpcClient>;
|
|
336
327
|
/**
|
|
337
|
-
* Context tag for the {@link AppRpcClient} seam. Provided by `@weftui/router
|
|
338
|
-
*
|
|
328
|
+
* Context tag for the {@link AppRpcClient} seam. Provided by `@weftui/router`:
|
|
329
|
+
* a network client (`RouterLive`, POST `/_eui/rpc`) on the client, an
|
|
339
330
|
* in-process client over the handler layer (`RouterServer`) on the server. Absent
|
|
340
331
|
* in a router-less mount, where a {@link Boundary.rpc} resolves to a descriptive
|
|
341
332
|
* "needs router/rpc" error.
|
|
@@ -345,7 +336,7 @@ declare class AppRpcClientTag extends AppRpcClientTag_base {}
|
|
|
345
336
|
//#region src/server/brand.d.ts
|
|
346
337
|
/**
|
|
347
338
|
* Type-level marker stamped onto the identifier of a {@link ServerTag}. It never
|
|
348
|
-
* exists at runtime
|
|
339
|
+
* exists at runtime (only its `typeof` is referenced by {@link ServerOnly}), so
|
|
349
340
|
* the brand is purely a compile-time discriminator for {@link AssertNoServerOnly}.
|
|
350
341
|
*/
|
|
351
342
|
declare const ServerOnlyTypeId: unique symbol;
|
|
@@ -398,10 +389,9 @@ type AssertNoServerOnly<R> = [Extract<R, ServerOnly>] extends [never] ? R : Serv
|
|
|
398
389
|
*/
|
|
399
390
|
declare function isStream(value: unknown): value is Stream.Stream<unknown, any, any>;
|
|
400
391
|
/**
|
|
401
|
-
* Normalizes a static value, `Effect`, or `Stream` into a `Stream
|
|
402
|
-
*
|
|
403
|
-
*
|
|
404
|
-
* unchanged.
|
|
392
|
+
* Normalizes a static value, `Effect`, or `Stream` into a `Stream`, the weft
|
|
393
|
+
* equivalent of Vue's `unref`. Static values become a single-element stream,
|
|
394
|
+
* Effects become a one-shot stream, and existing Streams pass through unchanged.
|
|
405
395
|
*/
|
|
406
396
|
declare function toStream<A>(value: A | Effect.Effect<A> | Stream.Stream<A>): Stream.Stream<A>;
|
|
407
397
|
//#endregion
|
|
@@ -409,16 +399,15 @@ declare function toStream<A>(value: A | Effect.Effect<A> | Stream.Stream<A>): St
|
|
|
409
399
|
/** Augmentable interface for user-defined custom element tags and props. */
|
|
410
400
|
interface CustomElements {}
|
|
411
401
|
/**
|
|
412
|
-
* Callable type for an element builder (one per tag
|
|
413
|
-
*
|
|
414
|
-
*
|
|
415
|
-
* `E`/`R` channels on the returned {@link Node}:
|
|
402
|
+
* Callable type for an element builder (one per tag: `h.div`, `h.span`, …).
|
|
403
|
+
* Every call shape preserves the caller's prop and child `E`/`R` channels on the
|
|
404
|
+
* returned {@link Node}:
|
|
416
405
|
*
|
|
417
|
-
* - `el(props, children)
|
|
418
|
-
* - `el(props, child)
|
|
419
|
-
* - `el(props)
|
|
420
|
-
* - `el(children)
|
|
421
|
-
* - `el()
|
|
406
|
+
* - `el(props, children)`: props plus an array of children.
|
|
407
|
+
* - `el(props, child)`: props plus a single `string | number` child.
|
|
408
|
+
* - `el(props)`: props only, no children.
|
|
409
|
+
* - `el(children)`: children only, no props.
|
|
410
|
+
* - `el()`: no arguments, yielding a `Node<never, never>`.
|
|
422
411
|
*/
|
|
423
412
|
interface ElementFn<Props> {
|
|
424
413
|
<P extends Props, const C extends readonly Renderable[]>(props: P, children: C): Node<PropsE<P> | ChildrenE<C>, PropsR<P> | ChildrenR<C>>;
|
|
@@ -433,14 +422,9 @@ type DataAttributes = {
|
|
|
433
422
|
};
|
|
434
423
|
type H = {
|
|
435
424
|
/**
|
|
436
|
-
* Builds a fragment node
|
|
437
|
-
*
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
* @example
|
|
441
|
-
* ```ts
|
|
442
|
-
* h.fragment([h.span({}, "left"), h.span({}, "right")]);
|
|
443
|
-
* ```
|
|
425
|
+
* Builds a fragment node whose children render inline, with no wrapping
|
|
426
|
+
* element. Equivalent to `<>…</>` in JSX. `E`/`R` from the children accumulate
|
|
427
|
+
* on the returned {@link Node}.
|
|
444
428
|
*/
|
|
445
429
|
fragment<const C extends readonly Renderable[]>(children: C): Node<ChildrenE<C>, ChildrenR<C>>;
|
|
446
430
|
} & { [K in keyof HTMLElements]: ElementFn<CombinatorialProps<HTMLElements[K] & DataAttributes>>; } & { [K in keyof SVGElements]: ElementFn<CombinatorialProps<SVGElements[K] & DataAttributes>>; } & { [K in keyof CustomElements]: ElementFn<CustomElements[K] & DataAttributes>; };
|
|
@@ -450,8 +434,8 @@ declare const h: H;
|
|
|
450
434
|
/**
|
|
451
435
|
* Builds a static-markup {@link Node} for a known {@link ElementDescriptor}.
|
|
452
436
|
*
|
|
453
|
-
* The result is a normal `Effect.succeed(descriptor)`
|
|
454
|
-
* inside `Component.gen` and indistinguishable to Effect
|
|
437
|
+
* The result is a normal `Effect.succeed(descriptor)` (fully `yield*`-able
|
|
438
|
+
* inside `Component.gen` and indistinguishable to Effect), but additionally
|
|
455
439
|
* carries the descriptor on a non-enumerable property. Renderers detect this via
|
|
456
440
|
* {@link getElementDescriptor} and render the descriptor directly, with no
|
|
457
441
|
* `runSync` probe (the reason `h.*`, `h.fragment`, and `Boundary.*` never need to
|
|
@@ -463,8 +447,8 @@ declare function elementNode<E = never, R = never>(descriptor: ElementDescriptor
|
|
|
463
447
|
* built with {@link elementNode}, or `undefined` if `node` is anything else
|
|
464
448
|
* (a primitive, an iterable, a `Stream`, or a reactive `Effect`).
|
|
465
449
|
*
|
|
466
|
-
* Renderers call this to take the static fast path
|
|
467
|
-
* directly
|
|
450
|
+
* Renderers call this to take the static fast path (rendering the descriptor
|
|
451
|
+
* directly) before falling back to running genuinely reactive Effects.
|
|
468
452
|
*/
|
|
469
453
|
declare function getElementDescriptor(node: unknown): ElementDescriptor | undefined;
|
|
470
454
|
//#endregion
|
|
@@ -479,7 +463,7 @@ declare const FRAGMENT: unique symbol;
|
|
|
479
463
|
* `FRAGMENT` and the `Boundary` symbols are detected.
|
|
480
464
|
*/
|
|
481
465
|
declare const LIST: unique symbol;
|
|
482
|
-
/** Element type carried by a list source
|
|
466
|
+
/** Element type carried by a list source: the element type of the emitted `Iterable`. */
|
|
483
467
|
type ItemOf<S> = Source.Success<S> extends Iterable<infer T> ? T : never;
|
|
484
468
|
/**
|
|
485
469
|
* Keyed-list combinator namespace. The opt-in alternative to wholesale child
|
|
@@ -507,30 +491,20 @@ declare namespace List {
|
|
|
507
491
|
readonly by?: (item: ItemOf<S>, index: number) => K;
|
|
508
492
|
}
|
|
509
493
|
/**
|
|
510
|
-
* Declares a keyed reactive list region.
|
|
511
|
-
*
|
|
512
|
-
*
|
|
513
|
-
*
|
|
514
|
-
* returned node's `E`/`R` are the union of the source channels and the
|
|
515
|
-
* channels of the node `render` returns.
|
|
516
|
-
*
|
|
517
|
-
* @example
|
|
518
|
-
* ```ts
|
|
519
|
-
* List.each(
|
|
520
|
-
* { of: peopleStream, by: (p) => p.id },
|
|
521
|
-
* (person) => h.li({}, person.name),
|
|
522
|
-
* );
|
|
523
|
-
* ```
|
|
494
|
+
* Declares a keyed reactive list region. `render` runs **once per key**; a
|
|
495
|
+
* persisted key keeps its DOM nodes and its running subscription fibers across
|
|
496
|
+
* re-emits (it is never re-invoked). The returned node's `E`/`R` are the union
|
|
497
|
+
* of the source channels and the channels of the node `render` returns.
|
|
524
498
|
*/
|
|
525
499
|
function each<S extends Source.Source<Iterable<any>, any, any>, CE = never, CR = never, K = ItemOf<S>>(options: Options<S, K>, render: (item: ItemOf<S>, index: number) => Node<CE, CR>): Node<Source.Error<S> | CE, Source.Context<S> | CR>;
|
|
526
500
|
/**
|
|
527
|
-
* Extract the error channel `E` from a list {@link Node}
|
|
501
|
+
* Extract the error channel `E` from a list {@link Node}, re-exported from
|
|
528
502
|
* {@link Node.Error}, the canonical accessor (a `Node`'s success channel is
|
|
529
503
|
* fixed to `ElementDescriptor`, so only `Error`/`Context` are exposed here).
|
|
530
504
|
*/
|
|
531
505
|
type Error<N> = Node.Error<N>;
|
|
532
506
|
/**
|
|
533
|
-
* Extract the requirement channel `R` from a list {@link Node}
|
|
507
|
+
* Extract the requirement channel `R` from a list {@link Node}, re-exported
|
|
534
508
|
* from {@link Node.Context}, the canonical accessor.
|
|
535
509
|
*/
|
|
536
510
|
type Context<N> = Node.Context<N>;
|
|
@@ -540,45 +514,39 @@ declare namespace List {
|
|
|
540
514
|
/**
|
|
541
515
|
* Factories for building custom components whose returned `Node`s carry the
|
|
542
516
|
* caller's prop `E`/`R` channels and the component's own internal `E`/`R`.
|
|
543
|
-
*
|
|
544
|
-
*
|
|
545
|
-
*
|
|
546
|
-
*
|
|
547
|
-
* - `Component.make` — body is a plain function returning any `Effect`.
|
|
548
|
-
*
|
|
549
|
-
* Both accept an optional second `children` argument which may be either an
|
|
550
|
-
* array of {@link Renderable} or a function `(input) => readonly Renderable[]` (the
|
|
551
|
-
* render-prop / function-children pattern).
|
|
517
|
+
* `Component.gen` takes a generator body (use `yield*` like `Effect.gen`);
|
|
518
|
+
* `Component.make` takes a plain function returning any `Effect`. Both accept an
|
|
519
|
+
* optional second `children` argument: an array of {@link Renderable}, or a
|
|
520
|
+
* function `(input) => readonly Renderable[]` (the render-prop pattern).
|
|
552
521
|
*/
|
|
553
522
|
declare namespace Component {
|
|
554
523
|
/**
|
|
555
524
|
* Shape of the optional `children` argument to a {@link Component}.
|
|
556
525
|
*
|
|
557
|
-
* - `readonly Renderable[]
|
|
558
|
-
* - `(input: Input) => readonly Renderable[]
|
|
526
|
+
* - `readonly Renderable[]`: a flat list of children, the common case.
|
|
527
|
+
* - `(input: Input) => readonly Renderable[]`: function-children. The component
|
|
559
528
|
* supplies `input` (some scoped value) and the caller returns the children
|
|
560
529
|
* array. Useful for render-prop / slot patterns.
|
|
561
530
|
*/
|
|
562
531
|
type Children<Input = never> = readonly Renderable[] | ((input: Input) => readonly Renderable[]);
|
|
563
532
|
/**
|
|
564
|
-
* Extract the error channel `E` from the {@link Node} a component produces
|
|
533
|
+
* Extract the error channel `E` from the {@link Node} a component produces,
|
|
565
534
|
* re-exported from {@link Node.Error}, the canonical accessor. Apply to a
|
|
566
535
|
* component's return type, e.g. `Component.Error<ReturnType<typeof MyComponent>>`.
|
|
567
536
|
*/
|
|
568
537
|
type Error<N> = Node.Error<N>;
|
|
569
538
|
/**
|
|
570
539
|
* Extract the requirement channel `R` from the {@link Node} a component
|
|
571
|
-
* produces
|
|
540
|
+
* produces, re-exported from {@link Node.Context}, the canonical accessor.
|
|
572
541
|
*/
|
|
573
542
|
type Context<N> = Node.Context<N>;
|
|
574
543
|
/**
|
|
575
544
|
* The callable shape returned by {@link gen} and {@link make}. Generic over
|
|
576
545
|
* the caller's specific `GenP`/`GenC` so reactive prop values and reactive
|
|
577
546
|
* children contribute their `E`/`R` to the resulting `Node` at the call site.
|
|
578
|
-
*
|
|
579
547
|
* For function-children, `ChildrenE`/`ChildrenR` are extracted from the
|
|
580
|
-
* function's `ReturnType`
|
|
581
|
-
*
|
|
548
|
+
* function's `ReturnType` (the array the caller would produce), not from the
|
|
549
|
+
* function itself.
|
|
582
550
|
*/
|
|
583
551
|
type Component<P, C extends Children, E, R> = <GenP extends P, GenC extends C>(props: GenP, children?: GenC) => Node<PropsE<GenP> | ChildrenE<GenC extends ((...args: any[]) => any) ? ReturnType<GenC> : GenC> | E, PropsR<GenP> | ChildrenR<GenC extends ((...args: any[]) => any) ? ReturnType<GenC> : GenC> | R>;
|
|
584
552
|
/**
|
|
@@ -612,30 +580,11 @@ declare namespace Component {
|
|
|
612
580
|
function gen<Eff extends Effect.Effect<any, any, any>, BaseProps = Record<string, never>, C extends Children = readonly Renderable[]>(f: (props: BaseProps, children: C) => Generator<Eff, ElementDescriptor, never>): Component.Component<BaseProps, C, Eff extends Effect.Effect<any, infer E, any> ? E : never, Eff extends Effect.Effect<any, any, infer R> ? R : never>;
|
|
613
581
|
/**
|
|
614
582
|
* Defines a component whose body is a plain function returning any `Effect`
|
|
615
|
-
* (typically a {@link Node}). Use when the implementation is a one-liner or
|
|
616
|
-
*
|
|
617
|
-
*
|
|
618
|
-
*
|
|
619
|
-
*
|
|
620
|
-
* call site, and function-children are supported via the optional second
|
|
621
|
-
* argument.
|
|
622
|
-
*
|
|
623
|
-
* @example
|
|
624
|
-
* ```ts
|
|
625
|
-
* const Avatar = Component.make((props: { src: string }) =>
|
|
626
|
-
* h.img({ src: props.src, alt: "" }),
|
|
627
|
-
* );
|
|
628
|
-
* ```
|
|
629
|
-
*
|
|
630
|
-
* @example With function-children
|
|
631
|
-
* ```ts
|
|
632
|
-
* const List = Component.make(
|
|
633
|
-
* (
|
|
634
|
-
* props: { items: readonly string[] },
|
|
635
|
-
* children: (item: string) => readonly Renderable[],
|
|
636
|
-
* ) => h.ul({}, props.items.flatMap(children)),
|
|
637
|
-
* );
|
|
638
|
-
* ```
|
|
583
|
+
* (typically a {@link Node}). Use when the implementation is a one-liner or a
|
|
584
|
+
* pipe composition, with no generator overhead. Same `E`/`R` propagation
|
|
585
|
+
* semantics as {@link gen}: internal `E`/`R` come from the returned effect,
|
|
586
|
+
* caller props and children contribute at the call site, and function-children
|
|
587
|
+
* are supported via the optional second argument.
|
|
639
588
|
*/
|
|
640
589
|
function make<Eff extends Effect.Effect<any, any, any>, BaseProps = Record<string, never>, C extends Children = readonly Renderable[]>(f: (props: BaseProps, children: C) => Eff): Component<BaseProps, C, Eff extends Effect.Effect<any, infer E, any> ? E : never, Eff extends Effect.Effect<any, any, infer R> ? R : never>;
|
|
641
590
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { C as StyleAttributeValue, S as HTMLAttributeSource, _ as HTMLFormMethod, a as SVGElements, b as HTMLReferrerPolicy, c as HTMLRole, d as EventHandlerFn, f as HTMLAutocapitalize, g as HTMLFormEncType, h as HTMLDir, i as SVGAttributes, l as DOMAttributes, m as HTMLCrossorigin, n as ElementType, o as HTMLAttributes, p as HTMLAutocomplete, r as Renderable, s as HTMLElements, t as ElementDescriptor, u as EventHandler, v as HTMLIframeSandbox, w as StyleProperties, x as AriaAttributes, y as HTMLLinkAs } from "../index-
|
|
1
|
+
import { C as StyleAttributeValue, S as HTMLAttributeSource, _ as HTMLFormMethod, a as SVGElements, b as HTMLReferrerPolicy, c as HTMLRole, d as EventHandlerFn, f as HTMLAutocapitalize, g as HTMLFormEncType, h as HTMLDir, i as SVGAttributes, l as DOMAttributes, m as HTMLCrossorigin, n as ElementType, o as HTMLAttributes, p as HTMLAutocomplete, r as Renderable, s as HTMLElements, t as ElementDescriptor, u as EventHandler, v as HTMLIframeSandbox, w as StyleProperties, x as AriaAttributes, y as HTMLLinkAs } from "../index-4cTlhojA.js";
|
|
2
2
|
export { AriaAttributes, DOMAttributes, ElementDescriptor, ElementType, EventHandler, EventHandlerFn, HTMLAttributeSource, HTMLAttributes, HTMLAutocapitalize, HTMLAutocomplete, HTMLCrossorigin, HTMLDir, HTMLElements, HTMLFormEncType, HTMLFormMethod, HTMLIframeSandbox, HTMLLinkAs, HTMLReferrerPolicy, HTMLRole, Renderable, SVGAttributes, SVGElements, StyleAttributeValue, StyleProperties };
|
|
@@ -2,18 +2,20 @@
|
|
|
2
2
|
title: Boundaries and Suspense
|
|
3
3
|
order: 4
|
|
4
4
|
section: explanation
|
|
5
|
-
description: How Weft models failure, async, and server data as boundary nodes in the same tree
|
|
5
|
+
description: How Weft models failure, async, and server data as boundary nodes in the same tree. Covers failure-catch variants, Boundary.suspend, and Boundary.rpc, and how their E/R channels behave.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Boundaries and Suspense
|
|
9
9
|
|
|
10
|
-
A **boundary** is a node that intercepts something flowing through the tree
|
|
10
|
+
A **boundary** is a node that intercepts something flowing through the tree: an error, a pending async child, or a server-resolved value. It decides what the DOM shows in its place.
|
|
11
|
+
|
|
12
|
+
A boundary is itself a `Node<E, R>` ([nodes are Effects](https://weftui.dev/docs/explanation/rendering-model)), so it composes exactly like any other element. You nest it, and its children's channels flow through it under a transformation the boundary defines.
|
|
11
13
|
|
|
12
14
|
The `Boundary` namespace has three kinds. This page is the conceptual map; the [core reference](https://weftui.dev/docs/reference/core#boundary-namespace) has the full signatures.
|
|
13
15
|
|
|
14
16
|
## Failure boundaries
|
|
15
17
|
|
|
16
|
-
A component's `E` channel accumulates up the tree. A **failure boundary** is where you _discharge_ some of that `E
|
|
18
|
+
A component's `E` channel accumulates up the tree. A **failure boundary** is where you _discharge_ some of that `E`. It wraps children and, if one of them fails, renders a fallback instead of letting the failure propagate to the mount.
|
|
17
19
|
|
|
18
20
|
```typescript
|
|
19
21
|
import { Boundary, h } from "@weftui/core";
|
|
@@ -32,19 +34,25 @@ There are six failure-catch variants, mirroring Effect's own error operators so
|
|
|
32
34
|
| `catchTag` / `catchTags` | one / several tagged errors by `_tag` |
|
|
33
35
|
| `catchFilter` / `catchIf` | a selected subset, by `Filter` / predicate |
|
|
34
36
|
|
|
35
|
-
The channel algebra is the whole reason they exist
|
|
37
|
+
The channel algebra is the whole reason they exist. `catchTag("Foo", …)` removes `Foo` from the children's `E` and adds whatever the fallback needs. The type of the boundary node therefore reflects exactly which failures are still live and which were handled.
|
|
38
|
+
|
|
39
|
+
An unhandled failure re-raises to the **nearest enclosing** boundary; if none catches it at **mount time**, mounting fails. Boundaries nest, so an inner `catchTag` can handle a specific case while an outer `catch` sweeps the rest.
|
|
36
40
|
|
|
37
41
|
### Post-mount failures with no enclosing boundary
|
|
38
42
|
|
|
39
|
-
The routing above describes what happens while a node is being built. Once mounted, a reactive region
|
|
43
|
+
The routing above describes what happens while a node is being built. Once mounted, a reactive region (an attribute, child, or list stream, or a hydrated equivalent) keeps running for the lifetime of its scope. It can still fail later: a `Stream` backing a `Boundary.rpc` resource might raise `RouterNotFound` after a client-side navigation. If a `BoundaryContext` encloses the region, the failure routes to it exactly as above, and the boundary's fallback swaps in.
|
|
44
|
+
|
|
45
|
+
If no boundary encloses it, there is nothing to swap to. Weft does not synthesize one. The region's DOM keeps its last rendered content, and a watcher fiber (forked into the same scope alongside the subscription itself) observes its exit directly.
|
|
46
|
+
|
|
47
|
+
When that exit is a failure whose cause is not interruption-only, Weft reports it explicitly via `Effect.logError(exit.cause)`. The log is annotated with `weft.region` to identify the failing region by kind and identity (e.g. `attribute:class`, `child:stream-3`, `list:stream-2`, `hydrate:stream-1 (/products/42)`). This fires for typed failures and defects alike, in both dev and prod, exactly once per failing region, at the `"Error"` level. Interruption (the ordinary case of unmount tearing down the region's scope) is never reported; only genuine failures are.
|
|
40
48
|
|
|
41
|
-
|
|
49
|
+
This is deliberate. Rather than leave the failure to whatever the Effect runtime would do with an unobserved fiber exit, Weft observes and logs it itself. Visibility is therefore controlled by the same knobs any Effect program uses: `References.MinimumLogLevel` (provided via `Effect.provideService`) to filter it, or a custom `Logger` to route it elsewhere.
|
|
42
50
|
|
|
43
|
-
|
|
51
|
+
A stream that can fail and has no enclosing boundary is a stream whose failures you've chosen not to route into the UI. The log is what tells you that decision has consequences at runtime.
|
|
44
52
|
|
|
45
53
|
## Suspense boundaries
|
|
46
54
|
|
|
47
|
-
`Boundary.suspend` wraps async children and shows a `fallback` until **all** of them have emitted their first value
|
|
55
|
+
`Boundary.suspend` wraps async children and shows a `fallback` until **all** of them have emitted their first value. Then it swaps atomically: either everything is visible or nothing is. This prevents partial flicker when sibling async regions resolve at different times.
|
|
48
56
|
|
|
49
57
|
```typescript
|
|
50
58
|
import { Boundary, h } from "@weftui/core";
|
|
@@ -55,15 +63,22 @@ Boundary.suspend({ fallback: h.div({ class: "spinner" }, "Loading…") }, [
|
|
|
55
63
|
]);
|
|
56
64
|
```
|
|
57
65
|
|
|
58
|
-
A suspense boundary is transparent to the type channels: its node is `Node<ChildrenE, ChildrenR
|
|
66
|
+
A suspense boundary is transparent to the type channels: its node is `Node<ChildrenE, ChildrenR>`. The children's `E`/`R` pass straight through, exactly as they would for a plain `h.*` parent. It changes _timing_ (when the children become visible), not _types_.
|
|
59
67
|
|
|
60
|
-
On the server, `renderToStreamHydratable` emits the fallback inline and appends patch scripts as children resolve
|
|
68
|
+
On the server, `renderToStreamHydratable` emits the fallback inline and appends patch scripts as children resolve. On the client, `hydrate` sees through the boundary and adopts the already-resolved DOM directly.
|
|
61
69
|
|
|
62
|
-
> **Note.** There is no `Suspense` export
|
|
70
|
+
> **Note.** There is no `Suspense` export; the API is `Boundary.suspend(props, children)`. Reach for it for async that loads **on the client**. For data that must resolve on the **server** and hydrate without a second request, use `Boundary.rpc` (below).
|
|
63
71
|
|
|
64
72
|
## The rpc boundary
|
|
65
73
|
|
|
66
|
-
`Boundary.rpc` is the server-data boundary:
|
|
74
|
+
`Boundary.rpc` is the server-data boundary. It:
|
|
75
|
+
|
|
76
|
+
- resolves one `Rpc` on the server
|
|
77
|
+
- serializes the result into the HTML
|
|
78
|
+
- replays it on the client during `hydrate` (no second request, no flash)
|
|
79
|
+
- keeps the region live for `refetch`
|
|
80
|
+
|
|
81
|
+
Conceptually it is the same idea as the other boundaries: a node that decides what renders in a subtree. But the thing it intercepts is a **round-trip to a server handler**. Instead of a children array, it takes a `render` function that receives a reactive [`Resource`](https://weftui.dev/docs/reference/core#resourcea).
|
|
67
82
|
|
|
68
83
|
```typescript
|
|
69
84
|
import { Boundary, h } from "@weftui/core";
|
|
@@ -77,15 +92,19 @@ Boundary.rpc(
|
|
|
77
92
|
);
|
|
78
93
|
```
|
|
79
94
|
|
|
80
|
-
Unlike the failure and suspense boundaries, `Boundary.rpc` is not self-contained
|
|
95
|
+
Unlike the failure and suspense boundaries, `Boundary.rpc` is not self-contained. It resolves through the ambient [`AppRpcClientTag`](https://weftui.dev/docs/reference/core#apprpcclienttag) seam that `@weftui/router` provides on both sides.
|
|
96
|
+
|
|
97
|
+
Its channel behavior is also distinct. The rpc's typed `error` schema joins the node's `E` (replayable through an enclosing failure boundary), while `render`'s `R` passes through untouched. The full model (the contract/handler split, the four lifecycles, typed-failure replay) is a **how-to**, not repeated here: [Load Data with RPC](https://weftui.dev/docs/how-to/load-data-with-rpc).
|
|
81
98
|
|
|
82
99
|
## One tree, three interceptors
|
|
83
100
|
|
|
84
|
-
The unifying idea: failure, async pending state, and server data are not three separate subsystems bolted onto the renderer. They are three **boundary nodes** in the one tree, each intercepting a different thing flowing through it
|
|
101
|
+
The unifying idea: failure, async pending state, and server data are not three separate subsystems bolted onto the renderer. They are three **boundary nodes** in the one tree, each intercepting a different thing flowing through it. Each has channel behavior you can read off its type.
|
|
102
|
+
|
|
103
|
+
That is why they nest freely. A `Boundary.catchTag` can wrap a `Boundary.rpc` to catch its typed failure. A `Boundary.suspend` can wrap async siblings that themselves contain rpc boundaries.
|
|
85
104
|
|
|
86
105
|
## See also
|
|
87
106
|
|
|
88
|
-
- [The Rendering Model](https://weftui.dev/docs/explanation/rendering-model)
|
|
89
|
-
- [`Boundary` API reference](https://weftui.dev/docs/reference/core#boundary-namespace)
|
|
90
|
-
- [Load Data with RPC](https://weftui.dev/docs/how-to/load-data-with-rpc)
|
|
91
|
-
- [Render on the Server](https://weftui.dev/docs/how-to/render-on-the-server)
|
|
107
|
+
- [The Rendering Model](https://weftui.dev/docs/explanation/rendering-model): why a boundary is just a node in a static tree
|
|
108
|
+
- [`Boundary` API reference](https://weftui.dev/docs/reference/core#boundary-namespace): every variant's signature and channel algebra
|
|
109
|
+
- [Load Data with RPC](https://weftui.dev/docs/how-to/load-data-with-rpc): the full `Boundary.rpc` walkthrough and its four lifecycles
|
|
110
|
+
- [Render on the Server](https://weftui.dev/docs/how-to/render-on-the-server): how suspense and rpc boundaries stream and hydrate
|
|
@@ -7,7 +7,9 @@ description: How h, h.fragment, and Component.gen / Component.make work; why Nod
|
|
|
7
7
|
|
|
8
8
|
# The Combinator API
|
|
9
9
|
|
|
10
|
-
Weft builds UI trees by calling builder functions. Because component return types stay as generic `Effect.Effect<ElementDescriptor, E, R>`, the error channel (`E`) and requirements channel (`R`) propagate through the entire tree
|
|
10
|
+
Weft builds UI trees by calling builder functions. Because component return types stay as generic `Effect.Effect<ElementDescriptor, E, R>`, the error channel (`E`) and requirements channel (`R`) propagate through the entire tree: visible to the type checker, satisfiable at the mount boundary.
|
|
11
|
+
|
|
12
|
+
JSX collapses every component's return type to an opaque `JSX.Element`, erasing both channels. The combinator API exists specifically to keep them intact.
|
|
11
13
|
|
|
12
14
|
## Nodes are Effects
|
|
13
15
|
|
|
@@ -23,13 +25,13 @@ Nodes are first-class Effects. Everything in the Effect ecosystem works on them
|
|
|
23
25
|
import { h } from "@weftui/core";
|
|
24
26
|
import { Effect } from "effect";
|
|
25
27
|
|
|
26
|
-
// yield* in Effect.gen
|
|
28
|
+
// yield* in Effect.gen: R propagates into the generator's context
|
|
27
29
|
const node = yield * h.div({ class: "container" }, "Hello");
|
|
28
30
|
|
|
29
|
-
// pipe
|
|
31
|
+
// pipe: chain Effect operators directly
|
|
30
32
|
const provided = pipe(h.div(userStream), Effect.provide(UserServiceLive));
|
|
31
33
|
|
|
32
|
-
// Effect.flatMap
|
|
34
|
+
// Effect.flatMap: sequence node creation with async logic
|
|
33
35
|
const card = pipe(
|
|
34
36
|
fetchCard(id),
|
|
35
37
|
Effect.flatMap((data) => h.div({ class: "card" }, data.title)),
|
|
@@ -78,7 +80,7 @@ Reactive prop values (any `Stream`, `Effect`, or `Subscribable`) contribute thei
|
|
|
78
80
|
```typescript
|
|
79
81
|
declare const colorStream: Stream.Stream<string, never, ThemeService>;
|
|
80
82
|
|
|
81
|
-
// Node<never, ThemeService
|
|
83
|
+
// Node<never, ThemeService>: R comes from the stream prop
|
|
82
84
|
const box = h.div({ style: { color: colorStream } }, "Hello");
|
|
83
85
|
```
|
|
84
86
|
|
|
@@ -130,7 +132,7 @@ declare const labelStream: Stream.Stream<string, never, I18nService>;
|
|
|
130
132
|
const btn = Button({ label: labelStream });
|
|
131
133
|
```
|
|
132
134
|
|
|
133
|
-
Components also accept an optional `children` argument, either as `readonly Renderable[]` or as a `(input) => readonly Renderable[]` function (render-prop pattern). `E`/`R` from children
|
|
135
|
+
Components also accept an optional `children` argument, either as `readonly Renderable[]` or as a `(input) => readonly Renderable[]` function (render-prop pattern). `E`/`R` from children, including the array returned by a function-children call, accumulate on the resulting node.
|
|
134
136
|
|
|
135
137
|
Without `Component`, a plain function's return type is fixed at definition time and does not reflect the caller's reactive prop types.
|
|
136
138
|
|
|
@@ -149,16 +151,16 @@ Boundary.suspend({ fallback: h.div({ class: "spinner" }, "Loading...") }, [
|
|
|
149
151
|
]);
|
|
150
152
|
```
|
|
151
153
|
|
|
152
|
-
The fallback is replaced atomically
|
|
154
|
+
The fallback is replaced atomically: either all children are visible or none are. This prevents partial flicker when multiple async siblings resolve at different times. The boundary's node type is `Node<ChildrenE, ChildrenR>`: the children's `E`/`R` channels accumulate onto it, exactly as they would for a plain `h.*` parent.
|
|
153
155
|
|
|
154
156
|
On the server, `renderToStreamHydratable` emits the fallback inline and appends patch scripts as children resolve. On the client, `hydrate` sees through `Boundary.suspend` boundaries and adopts the already-resolved DOM directly.
|
|
155
157
|
|
|
156
|
-
`Boundary.suspend` is one of the boundary combinators
|
|
158
|
+
`Boundary.suspend` is one of the boundary combinators. See the [core reference](https://weftui.dev/docs/reference/core#boundarysuspend) for the full `Boundary.*` surface, including the failure-catch variants and `Boundary.rpc`.
|
|
157
159
|
|
|
158
160
|
## See also
|
|
159
161
|
|
|
160
|
-
- [The Rendering Model](https://weftui.dev/docs/explanation/rendering-model)
|
|
161
|
-
- [Reactive Primitives](https://weftui.dev/docs/explanation/reactive-primitives)
|
|
162
|
-
- [Boundaries and Suspense](https://weftui.dev/docs/explanation/boundaries-and-suspense)
|
|
163
|
-
- [Author Components](https://weftui.dev/docs/how-to/author-components)
|
|
162
|
+
- [The Rendering Model](https://weftui.dev/docs/explanation/rendering-model): why a `Node` is an `Effect` and how the tree renders
|
|
163
|
+
- [Reactive Primitives](https://weftui.dev/docs/explanation/reactive-primitives): the `Source` vocabulary that reactive props and children accept
|
|
164
|
+
- [Boundaries and Suspense](https://weftui.dev/docs/explanation/boundaries-and-suspense): the boundary combinators as tree nodes
|
|
165
|
+
- [Author Components](https://weftui.dev/docs/how-to/author-components): `Component.gen` / `Component.make` in practice
|
|
164
166
|
- [`@weftui/core` reference](https://weftui.dev/docs/reference/core)
|