@weftui/core 0.27.1 → 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 +11 -10
- package/dist/{index-DMNuAQXj.d.ts → index-4cTlhojA.d.ts} +47 -35
- package/dist/index.d.ts +77 -135
- 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 +15 -15
- package/docs/explanation/rendering-model.md +20 -18
- package/docs/explanation/services-and-context.md +39 -29
- package/docs/how-to/add-routing.md +76 -52
- package/docs/how-to/author-components.md +46 -32
- 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 +34 -30
- package/docs/how-to/provide-services.md +54 -74
- package/docs/how-to/render-keyed-lists.md +10 -8
- package/docs/how-to/render-on-the-server.md +19 -14
- 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 +395 -58
- package/docs/reference/router.md +71 -49
- package/docs/tutorial/01-your-first-app.md +10 -11
- package/docs/tutorial/02-reactivity.md +11 -8
- package/docs/tutorial/03-services-and-async.md +19 -13
- package/docs/tutorial/04-errors-and-server.md +17 -7
- package/package.json +8 -7
|
@@ -9,7 +9,9 @@ description: Render a pending indicator (e.g. a top progress bar) during a defer
|
|
|
9
9
|
|
|
10
10
|
**Goal:** show a progress indicator while a [lazy route](https://weftui.dev/docs/how-to/split-routes-lazily) resolves its chunk and data, so a slow network is visible instead of feeling frozen.
|
|
11
11
|
|
|
12
|
-
When you navigate to a route, the router is **deferred-commit
|
|
12
|
+
When you navigate to a route, the router is **deferred-commit**. It resolves the target branch's chunk (if the component is `Router.lazy`) **and the matched leaf's own component effect**, including any data the leaf awaits in its body. Only then does it swap the URL, keeping the previous page mounted for the whole window.
|
|
13
|
+
|
|
14
|
+
That resolve window is exposed as a reactive signal, [`Router.navigating`](https://weftui.dev/docs/reference/router#routernavigating), that you read to render pending UI.
|
|
13
15
|
|
|
14
16
|
```typescript
|
|
15
17
|
import { Component, h } from "@weftui/core";
|
|
@@ -32,7 +34,7 @@ const Shell = Component.gen(function* () {
|
|
|
32
34
|
});
|
|
33
35
|
```
|
|
34
36
|
|
|
35
|
-
Thread the signal into a persistent layout (the outermost `Shell` is ideal, since it never re-renders across navigations), and style the pending class however you like
|
|
37
|
+
Thread the signal into a persistent layout (the outermost `Shell` is ideal, since it never re-renders across navigations), and style the pending class however you like: a top bar, a cursor change, a dimmed outlet.
|
|
36
38
|
|
|
37
39
|
## The signal
|
|
38
40
|
|
|
@@ -44,22 +46,22 @@ type NavState = { readonly _tag: "Idle" } | { readonly _tag: "Navigating"; reado
|
|
|
44
46
|
|
|
45
47
|
Read it two ways, mirroring `Router.params` / `Router.paramsStream`:
|
|
46
48
|
|
|
47
|
-
- `Router.navigating
|
|
48
|
-
- `Router.navigatingStream
|
|
49
|
+
- `Router.navigating`: the `Subscribable<NavState>` on the `Router` service.
|
|
50
|
+
- `Router.navigatingStream`: an `Effect` resolving that `Subscribable`, for use in a `Component.gen` body (as above).
|
|
49
51
|
|
|
50
52
|
The `to` field on `Navigating` is the target URL, if you want to label _where_ the app is going.
|
|
51
53
|
|
|
52
54
|
## Behavior to expect
|
|
53
55
|
|
|
54
|
-
- **Only navigations with real async work flip it.** A branch with no `Router.lazy` node and a leaf whose effect resolves synchronously (no async work, or a memoized revisit) commits in the same tick, and `navigating` stays `Idle
|
|
56
|
+
- **Only navigations with real async work flip it.** A branch with no `Router.lazy` node and a leaf whose effect resolves synchronously (no async work, or a memoized revisit) commits in the same tick, and `navigating` stays `Idle`. An entirely eager app never sees `Navigating`, and adding the reader costs nothing.
|
|
55
57
|
- **Latest-wins.** Rapid successive navigations commit only the newest; a superseded navigation never resets the signal (the newer one owns it).
|
|
56
58
|
- **Back/forward.** `popstate` into a route with async work also resolves before committing, so the indicator shows for browser back/forward too.
|
|
57
59
|
- **Failure resets it.** A rejected chunk load or a failing leaf pre-run (a typed error such as `notFound()`, or a defect) resets `navigating` to `Idle` (it never sticks on), then surfaces through normal error/defect handling.
|
|
58
60
|
- **Server renders `Idle`.** Server render is buffered, so `navigating` is a client-only concern; the server supplies a constant `Idle` so the same `Shell` type-checks and renders on both sides.
|
|
59
|
-
- **No built-in anti-flash delay.** The signal flips as soon as an async window opens, so a borderline-fast navigation can flash the indicator briefly. If you want to only show it past a threshold, delay the reveal in CSS rather than in the signal
|
|
61
|
+
- **No built-in anti-flash delay.** The signal flips as soon as an async window opens, so a borderline-fast navigation can flash the indicator briefly. If you want to only show it past a threshold, delay the reveal in CSS rather than in the signal (e.g. `transition-delay: 200ms` on `.is-navigating`), so genuinely fast navigations never flicker.
|
|
60
62
|
|
|
61
63
|
## See also
|
|
62
64
|
|
|
63
65
|
- [`Router.navigating` API reference](https://weftui.dev/docs/reference/router#routernavigating)
|
|
64
|
-
- [Split Routes Lazily](https://weftui.dev/docs/how-to/split-routes-lazily)
|
|
65
|
-
- [examples/router-ssr](https://github.com/stefvw93/weft/tree/main/examples/router-ssr)
|
|
66
|
+
- [Split Routes Lazily](https://weftui.dev/docs/how-to/split-routes-lazily): the `Router.lazy` deferred-commit navigation this reports on
|
|
67
|
+
- [examples/router-ssr](https://github.com/stefvw93/weft/tree/main/examples/router-ssr): wires this exact progress bar in its `Shell` (`components/shell.ts`), with a `pending-navigation.browser.test.ts`
|
|
@@ -9,7 +9,7 @@ description: Code-split a route's component into its own chunk with Router.lazy,
|
|
|
9
9
|
|
|
10
10
|
**Goal:** keep a heavy page's render code (and its dependencies) out of the initial bundle, loading it only when its route is actually rendered.
|
|
11
11
|
|
|
12
|
-
Wrap the route's `component` in [`Router.lazy`](https://weftui.dev/docs/reference/router#routerlazy). The route **descriptor** (its segment and param schemas) stays eager so the matcher, `href`, and the server's dispatch API still see it statically
|
|
12
|
+
Wrap the route's `component` in [`Router.lazy`](https://weftui.dev/docs/reference/router#routerlazy). The route **descriptor** (its segment and param schemas) stays eager, so the matcher, `href`, and the server's dispatch API still see it statically. Only the component body is split into its own chunk.
|
|
13
13
|
|
|
14
14
|
```typescript
|
|
15
15
|
import { Router } from "@weftui/router";
|
|
@@ -21,42 +21,44 @@ Router.route("docs/:category/:slug", {
|
|
|
21
21
|
});
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
The chunk loads on the server during render and on the client on navigation
|
|
24
|
+
The chunk loads on the server during render and on the client on navigation. Only the **matched branch's** chunks are ever fetched.
|
|
25
|
+
|
|
26
|
+
`E`/`R` are preserved: a lazy route has the exact same channels as the same component declared eagerly. An unmet service requirement is still a compile error at `Router.router(...)`.
|
|
25
27
|
|
|
26
28
|
## Make the split real
|
|
27
29
|
|
|
28
|
-
`Router.lazy` only splits if the dynamic `import()` is the **only eager path** to the heavy module. Keep the `Router.route(…)` descriptor in an eagerly-imported file
|
|
30
|
+
`Router.lazy` only splits if the dynamic `import()` is the **only eager path** to the heavy module. Keep the `Router.route(…)` descriptor in an eagerly-imported file. Move the component implementation (and its heavy deps) into a separate module referenced _only_ through `Router.lazy(() => import("./impl"))`:
|
|
29
31
|
|
|
30
32
|
```typescript
|
|
31
|
-
// routes.ts
|
|
33
|
+
// routes.ts: eager and tiny, just the descriptor
|
|
32
34
|
export const docsRoute = Router.route("docs/:category/:slug", {
|
|
33
35
|
path: { category: Schema.String, slug: Schema.String },
|
|
34
36
|
component: Router.lazy(() => import("./doc-page-impl").then((m) => m.DocsPage)),
|
|
35
37
|
});
|
|
36
38
|
|
|
37
|
-
// doc-page-impl.ts
|
|
39
|
+
// doc-page-impl.ts: heavy, pulled into its own chunk, never in the initial graph
|
|
38
40
|
export const DocsPage = Component.gen(function* () {
|
|
39
41
|
/* renderHast, code highlighting, … */
|
|
40
42
|
});
|
|
41
43
|
```
|
|
42
44
|
|
|
43
|
-
A descriptor file that still `import`s the impl statically gains nothing
|
|
45
|
+
A descriptor file that still `import`s the impl statically gains nothing: the bundler keeps it in the initial graph.
|
|
44
46
|
|
|
45
47
|
## What you get for free
|
|
46
48
|
|
|
47
|
-
- **Flash-free hydration.** On a directly-loaded lazy route, the client re-invokes the same slot, awaits the chunk, and adopts the server DOM in place
|
|
48
|
-
- **Blank-free navigation.** Client navigation is **deferred-commit**: the router resolves the target branch's chunk **and the matched leaf's own component effect** _before_ committing the URL
|
|
49
|
+
- **Flash-free hydration.** On a directly-loaded lazy route, the client re-invokes the same slot, awaits the chunk, and adopts the server DOM in place. The first production matches, so nothing is mutated.
|
|
50
|
+
- **Blank-free navigation.** Client navigation is **deferred-commit**: the router resolves the target branch's chunk **and the matched leaf's own component effect** _before_ committing the URL. The previous page stays mounted through the fetch and any data the leaf awaits, and the swap is a single tick. See [Show Navigation Progress](https://weftui.dev/docs/how-to/show-navigation-progress) for the `Router.navigating` signal this exposes.
|
|
49
51
|
- **Synchronous revisits.** `Router.lazy` memoizes its load per slot, so a second visit to a loaded route commits immediately.
|
|
50
52
|
|
|
51
53
|
## Edge cases
|
|
52
54
|
|
|
53
|
-
- **Lazy layouts.** A `Router.layout({ component: Router.lazy(...) })` splits too
|
|
54
|
-
- **Chunk-load failure is a defect.** If the `import()` rejects (offline, or a stale client requesting a chunk a new deploy removed), it dies as a defect and surfaces through normal defect handling
|
|
55
|
-
- **Not a lazy _subtree_.** Only the component is lazy
|
|
55
|
+
- **Lazy layouts.** A `Router.layout({ component: Router.lazy(...) })` splits too. Each lazy node in the matched branch is awaited; nodes outside it never load.
|
|
56
|
+
- **Chunk-load failure is a defect.** If the `import()` rejects (offline, or a stale client requesting a chunk a new deploy removed), it dies as a defect and surfaces through normal defect handling. It never hangs or silently 404s. The rejection is memoized, so the route keeps failing until a reload (the deploy-skew case).
|
|
57
|
+
- **Not a lazy _subtree_.** Only the component is lazy. You cannot defer a whole `RouteNode` behind an `import()`; the matcher needs every leaf's segment and param schema before anything loads.
|
|
56
58
|
|
|
57
59
|
## See also
|
|
58
60
|
|
|
59
61
|
- [`Router.lazy` API reference](https://weftui.dev/docs/reference/router#routerlazy)
|
|
60
|
-
- [Show Navigation Progress](https://weftui.dev/docs/how-to/show-navigation-progress)
|
|
61
|
-
- [Add Routing](https://weftui.dev/docs/how-to/add-routing)
|
|
62
|
-
- [examples/router-ssr](https://github.com/stefvw93/weft/tree/main/examples/router-ssr)
|
|
62
|
+
- [Show Navigation Progress](https://weftui.dev/docs/how-to/show-navigation-progress): the deferred-commit `Router.navigating` signal
|
|
63
|
+
- [Add Routing](https://weftui.dev/docs/how-to/add-routing): authoring the route tree `Router.lazy` plugs into
|
|
64
|
+
- [examples/router-ssr](https://github.com/stefvw93/weft/tree/main/examples/router-ssr): includes a `Router.lazy` page (`lazy-page.ts`) with a browser test
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
40
|
-
2. **A static object
|
|
41
|
-
3. **A whole style object as a stream
|
|
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`)
|
|
57
|
-
- **CSS transitions just work.**
|
|
58
|
-
- **Pace with `Schedule`.** `Stream.iterate`/`Stream.make` paced by `Stream.schedule(Schedule.spaced(…))` and looped with `Stream.forever
|
|
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)
|
|
63
|
-
- [
|
|
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
|
|
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
|
|
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
|
|
38
|
-
- **React to mount** by observing `SubscriptionRef.changes(ref)
|
|
39
|
-
- **Use `Effect.forkScoped`, not `Effect.forkChild`.** `forkScoped` ties the observer fiber to the component's **instance scope** (the ambient `Scope` the renderer provides)
|
|
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)
|
|
62
|
-
- [Author Components](https://weftui.dev/docs/how-to/author-components)
|
|
63
|
-
- [
|
|
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
|
|
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)
|
|
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)
|
|
14
|
-
2. [Reactivity](https://weftui.dev/docs/tutorial/02-reactivity)
|
|
15
|
-
3. [Services and Async](https://weftui.dev/docs/tutorial/03-services-and-async)
|
|
16
|
-
4. [Errors and Server Rendering](https://weftui.dev/docs/tutorial/04-errors-and-server)
|
|
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
|
|
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)
|
|
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
|
|
34
|
-
- **`@weftui/dom
|
|
35
|
-
- **`@weftui/router
|
|
36
|
-
- **`@weftui/vite
|
|
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
|
|
40
|
+
`@weftui/base` is an internal, currently-empty stub with no public primitives. Ignore it.
|
|
39
41
|
|
|
40
42
|
## Examples
|
|
41
43
|
|
package/docs/reference/core.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
121
|
-
- Post-mount stream failures
|
|
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
|
|
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
|
|
161
|
-
- **Hydrate:** `hydrate` reads the inline payload positionally, `successSchema`-decodes it, seeds the `Resource`, and adopts the DOM
|
|
162
|
-
- **Refetch:** `resource.refetch` calls the rpc again over the network (`POST /_eui/rpc`) and patches the subtree in place
|
|
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
|
|
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
|
|
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
|
|
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
|
|
178
|
-
| `refetch` | `Effect.Effect<void>` | Re-resolves the rpc over the network with a fresh `payload()` and sets `value`. Client only
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
340
|
-
- If a branded tag ever reaches client code
|
|
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
|
|
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
|
|
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
|
|
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
|
|
387
|
-
- **`by
|
|
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
|
|
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.
|
|
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
|
|
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)
|
|
506
|
-
- [Author Components](https://weftui.dev/docs/how-to/author-components) · [Render Keyed Lists](https://weftui.dev/docs/how-to/render-keyed-lists)
|
|
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)
|