@weftui/router 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 +3 -3
  2. package/dist/client/index.d.ts +19 -19
  3. package/dist/client/index.js +15 -15
  4. package/dist/{compile-DySac7KN.d.ts → compile-Bb7AknG_.d.ts} +37 -57
  5. package/dist/{href-uvJ6b7zz.js → href-3swSSbn_.js} +7 -10
  6. package/dist/index.d.ts +2 -2
  7. package/dist/index.js +2 -2
  8. package/dist/{outlet-saKduDFz.d.ts → outlet-2AnUWKQD.d.ts} +1 -1
  9. package/dist/{outlet-C9N4a4_F.js → outlet-BgJmsO_g.js} +38 -46
  10. package/dist/server/index.d.ts +9 -9
  11. package/dist/server/index.js +10 -10
  12. package/docs/explanation/boundaries-and-suspense.md +37 -18
  13. package/docs/explanation/combinator-api.md +14 -12
  14. package/docs/explanation/reactive-primitives.md +14 -14
  15. package/docs/explanation/rendering-model.md +20 -18
  16. package/docs/explanation/services-and-context.md +35 -21
  17. package/docs/how-to/add-routing.md +72 -48
  18. package/docs/how-to/author-components.md +40 -28
  19. package/docs/how-to/compose-behavior-and-markup.md +144 -0
  20. package/docs/how-to/handle-forms.md +6 -6
  21. package/docs/how-to/load-async-data.md +15 -13
  22. package/docs/how-to/load-data-with-rpc.md +30 -28
  23. package/docs/how-to/provide-services.md +20 -18
  24. package/docs/how-to/render-keyed-lists.md +10 -8
  25. package/docs/how-to/render-on-the-server.md +16 -12
  26. package/docs/how-to/show-navigation-progress.md +10 -8
  27. package/docs/how-to/split-routes-lazily.md +16 -14
  28. package/docs/how-to/style-reactively.md +13 -13
  29. package/docs/how-to/use-element-refs.md +10 -8
  30. package/docs/index.md +20 -18
  31. package/docs/reference/core.md +44 -40
  32. package/docs/reference/dom.md +274 -58
  33. package/docs/reference/router.md +69 -47
  34. package/docs/tutorial/01-your-first-app.md +7 -9
  35. package/docs/tutorial/02-reactivity.md +8 -6
  36. package/docs/tutorial/03-services-and-async.md +10 -6
  37. package/docs/tutorial/04-errors-and-server.md +14 -5
  38. package/package.json +4 -4
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @weftui/router
2
2
 
3
- > Universal nested router for [Weft](https://weftui.dev) one route tree, rendered on the server and the client, with type-safe params and `href`s.
3
+ > Universal nested router for [Weft](https://weftui.dev): one route tree, rendered on the server and the client, with type-safe params and `href`s.
4
4
 
5
5
  Maps a URL to a nested page tree that renders identically on the server (`@weftui/router/server`) and the client (`@weftui/router/client`). Route params and query are decoded through [Effect Schema](https://effect.website/docs/schema/introduction/), `href` builds type-safe URLs that round-trip with the matcher, layouts persist across navigations, and `Router.lazy` code-splits a branch while keeping its descriptor eager.
6
6
 
@@ -26,7 +26,7 @@ Weft tracks Effect 4's beta line. This release is built and tested against `effe
26
26
  | `Router.params` / `Router.query` | Read the live match (snapshot); `…Stream` variants for reactive query-in-place updates. |
27
27
  | `Router.navigating` | Reactive `Idle`/`Navigating` signal for pending UI during deferred-commit navigation. |
28
28
  | `href(ref, args)` | Builds a type-safe URL for a leaf route reference. |
29
- | `RouterApp` / `RouterOutlet` | The universal router root node render on both server and client. |
29
+ | `RouterApp` / `RouterOutlet` | The universal router root node: render on both server and client. |
30
30
  | `RouterLive` (client) | History-backed `Router` layer; also provides the `AppRpcClientTag` seam. |
31
31
  | `RouterServer` (server) | `RouterServer.render` / `RouterServer.toWebHandler` for SSR dispatch. |
32
32
 
@@ -54,7 +54,7 @@ href(userRoute, { path: { id: 42 } }); // "/users/42"
54
54
  - Full docs: **https://weftui.dev**
55
55
  - `@weftui/router` API reference: **https://weftui.dev/docs/reference/router**
56
56
  - Routing guide: **https://weftui.dev/docs/how-to/add-routing**
57
- - Bundled with this package: see the [`./docs`](./docs) directory in `node_modules/@weftui/router/docs` the complete tutorial, how-to, explanation, and reference tree ships on disk for offline and agent use.
57
+ - Bundled with this package: see the [`./docs`](./docs) directory in `node_modules/@weftui/router/docs`. The complete tutorial, how-to, explanation, and reference tree ships on disk for offline and agent use.
58
58
 
59
59
  ## License
60
60
 
@@ -1,17 +1,17 @@
1
- import { S as Router, b as NavState, i as RouterDef, m as RouteNode, u as Fields, x as NavigateOptions } from "../compile-DySac7KN.js";
2
- import { n as outletNode, r as HrefArgs, t as RouterApp } from "../outlet-saKduDFz.js";
1
+ import { S as Router, b as NavState, i as RouterDef, m as RouteNode, u as Fields, x as NavigateOptions } from "../compile-Bb7AknG_.js";
2
+ import { n as outletNode, r as HrefArgs, t as RouterApp } from "../outlet-2AnUWKQD.js";
3
3
  import { Effect, Layer, Scope } from "effect";
4
4
  import { AppRpcClientTag } from "@weftui/core";
5
5
  import { RpcGroup } from "effect/unstable/rpc";
6
6
  //#region src/client/router-live.d.ts
7
7
  /**
8
8
  * The residual app services a caller must still provide through the {@link RouterLiveOptions.context}
9
- * seam a def's aggregate `R` minus the services `RouterLive` already threads
9
+ * seam: a def's aggregate `R` minus the services `RouterLive` already threads
10
10
  * (`Router`, `Router.Outlet`, `AppRpcClientTag`). The client mirror of
11
11
  * `RouterServer.AppServices`; resolves to `never` for an app with no app-wide service.
12
12
  */
13
13
  type AppServices<R> = Exclude<R, Router | Router.Outlet | AppRpcClientTag>;
14
- /** True only for the exact `any` type a loosely-typed `RouterDef<any, any>`. */
14
+ /** True only for the exact `any` type (a loosely-typed `RouterDef<any, any>`). */
15
15
  type IsAny<T> = 0 extends 1 & T ? true : false;
16
16
  /**
17
17
  * Conditionally shapes the `context` field: **required** when the def has statically
@@ -37,8 +37,8 @@ interface RouterLiveOptions {
37
37
  /**
38
38
  * The app's `Boundary.rpc` foundation: the merged `RpcGroup` contract (shared
39
39
  * with the server handler Layer). Backs the {@link AppRpcClientTag} seam so a
40
- * hydrated boundary refetch and a client-first SPA mount resolve over the
41
- * network rpc client. Optional: omit when the app has no `Boundary.rpc` — then
40
+ * hydrated boundary refetch (and a client-first SPA mount) resolve over the
41
+ * network rpc client. Optional: omit when the app has no `Boundary.rpc`. Then
42
42
  * no network rpc client is built and a stray `Boundary.rpc` fails with a
43
43
  * descriptive error.
44
44
  */
@@ -57,11 +57,11 @@ interface RouterLiveOptions {
57
57
  * It additionally derives a real {@link RouterHttpApiClient} from `def.httpApi`
58
58
  * (over `FetchHttpClient`, `baseUrl` default same-origin) and exposes it on the
59
59
  * `Router` service for network work. SPA URL→leaf resolution stays local via the
60
- * shared {@link match}er both sides read the one `def.httpApi` definition.
60
+ * shared {@link match}er: both sides read the one `def.httpApi` definition.
61
61
  *
62
- * Alongside `Router` it provides the core {@link AppRpcClientTag} seam a
62
+ * Alongside `Router` it provides the core {@link AppRpcClientTag} seam: a
63
63
  * **network** flat rpc client (`RpcClient.make` over `layerProtocolHttp` →
64
- * `POST /_eui/rpc`) so `@weftui/dom` can resolve a `Boundary.rpc` (hydrated
64
+ * `POST /_eui/rpc`), so `@weftui/dom` can resolve a `Boundary.rpc` (hydrated
65
65
  * refetch and client-first mount) without depending on this package or
66
66
  * `effect/unstable/rpc`.
67
67
  */
@@ -74,8 +74,8 @@ declare function RouterLive<R>(def: RouterDef<any, R>, options?: RouterLiveOptio
74
74
  * the href resolves to a route (L1). Modified clicks, non-left buttons,
75
75
  * `target=_blank`, `download`, external origins, same-document (hash-only or
76
76
  * identical-URL) navigations, and non-matching hrefs fall through to the
77
- * browser's native handling the interceptor leaves `preventDefault` untouched
78
- * in those cases (L2). The listener is removed on scope teardown (L3).
77
+ * browser's native handling. In those cases the interceptor leaves
78
+ * `preventDefault` untouched (L2). The listener is removed on scope teardown (L3).
79
79
  *
80
80
  * @param def - The router definition, used to decide whether an href matches a route.
81
81
  * @param navigate - The router's `navigate`, run via the captured runtime on a match.
@@ -88,10 +88,10 @@ declare function installLinkInterceptor(def: RouterDef, navigate: (to: string) =
88
88
  * type-safe {@link href} builder. They mirror the History API the client `Router`
89
89
  * layer (`RouterLive`) is backed by:
90
90
  *
91
- * - {@link navigate} go to a leaf route reference with typed `{ path, query }`.
92
- * - {@link push} / {@link replace} go to a raw `path + search` string.
93
- * - {@link back} / {@link forward} step through History (`history.go`).
94
- * - {@link setQuery} / {@link patchQuery} change the current route's query in
91
+ * - {@link navigate}: go to a leaf route reference with typed `{ path, query }`.
92
+ * - {@link push} / {@link replace}: go to a raw `path + search` string.
93
+ * - {@link back} / {@link forward}: step through History (`history.go`).
94
+ * - {@link setQuery} / {@link patchQuery}: change the current route's query in
95
95
  * place, re-encoding through the matched leaf's `querySchema`.
96
96
  *
97
97
  * All but `back`/`forward` require the `Router` service (run them within the layer
@@ -99,10 +99,10 @@ declare function installLinkInterceptor(def: RouterDef, navigate: (to: string) =
99
99
  */
100
100
  /**
101
101
  * Navigates to a leaf route `ref` with typed `path`/`query` args, building the URL
102
- * via {@link href} (so it round-trips with `match`) and pushing or, with
103
- * `options.replace`, replacing the History entry. `path` is required when the
104
- * route has path params; `query` is optional when every query field is optional
105
- * (same requiredness rules as `href`).
102
+ * via {@link href} (so it round-trips with `match`) and pushing the History entry,
103
+ * or replacing it with `options.replace`. `path` is required when the route has
104
+ * path params; `query` is optional when every query field is optional (same
105
+ * requiredness rules as `href`).
106
106
  *
107
107
  * @example
108
108
  * ```ts
@@ -1,5 +1,5 @@
1
- import { a as Router, i as setResolvedCommit, n as outletNode, o as getPreload, r as preRunLeaf, t as RouterApp } from "../outlet-C9N4a4_F.js";
2
- import { r as match, t as href } from "../href-uvJ6b7zz.js";
1
+ import { a as Router, i as setResolvedCommit, n as outletNode, o as getPreload, r as preRunLeaf, t as RouterApp } from "../outlet-BgJmsO_g.js";
2
+ import { r as match, t as href } from "../href-3swSSbn_.js";
3
3
  import { HttpApiClient } from "effect/unstable/httpapi";
4
4
  import { Context, Effect, Exit, Fiber, Layer, Option, Schema, Scope, Stream, SubscriptionRef } from "effect";
5
5
  import { AppRpcClientTag, Subscribable } from "@weftui/core";
@@ -11,8 +11,8 @@ import { FetchHttpClient } from "effect/unstable/http";
11
11
  * the href resolves to a route (L1). Modified clicks, non-left buttons,
12
12
  * `target=_blank`, `download`, external origins, same-document (hash-only or
13
13
  * identical-URL) navigations, and non-matching hrefs fall through to the
14
- * browser's native handling the interceptor leaves `preventDefault` untouched
15
- * in those cases (L2). The listener is removed on scope teardown (L3).
14
+ * browser's native handling. In those cases the interceptor leaves
15
+ * `preventDefault` untouched (L2). The listener is removed on scope teardown (L3).
16
16
  *
17
17
  * @param def - The router definition, used to decide whether an href matches a route.
18
18
  * @param navigate - The router's `navigate`, run via the captured runtime on a match.
@@ -68,11 +68,11 @@ function normalizeTo(to) {
68
68
  * It additionally derives a real {@link RouterHttpApiClient} from `def.httpApi`
69
69
  * (over `FetchHttpClient`, `baseUrl` default same-origin) and exposes it on the
70
70
  * `Router` service for network work. SPA URL→leaf resolution stays local via the
71
- * shared {@link match}er both sides read the one `def.httpApi` definition.
71
+ * shared {@link match}er: both sides read the one `def.httpApi` definition.
72
72
  *
73
- * Alongside `Router` it provides the core {@link AppRpcClientTag} seam a
73
+ * Alongside `Router` it provides the core {@link AppRpcClientTag} seam: a
74
74
  * **network** flat rpc client (`RpcClient.make` over `layerProtocolHttp` →
75
- * `POST /_eui/rpc`) so `@weftui/dom` can resolve a `Boundary.rpc` (hydrated
75
+ * `POST /_eui/rpc`), so `@weftui/dom` can resolve a `Boundary.rpc` (hydrated
76
76
  * refetch and client-first mount) without depending on this package or
77
77
  * `effect/unstable/rpc`.
78
78
  */
@@ -187,10 +187,10 @@ function RouterLive(def, options = {}) {
187
187
  * type-safe {@link href} builder. They mirror the History API the client `Router`
188
188
  * layer (`RouterLive`) is backed by:
189
189
  *
190
- * - {@link navigate} go to a leaf route reference with typed `{ path, query }`.
191
- * - {@link push} / {@link replace} go to a raw `path + search` string.
192
- * - {@link back} / {@link forward} step through History (`history.go`).
193
- * - {@link setQuery} / {@link patchQuery} change the current route's query in
190
+ * - {@link navigate}: go to a leaf route reference with typed `{ path, query }`.
191
+ * - {@link push} / {@link replace}: go to a raw `path + search` string.
192
+ * - {@link back} / {@link forward}: step through History (`history.go`).
193
+ * - {@link setQuery} / {@link patchQuery}: change the current route's query in
194
194
  * place, re-encoding through the matched leaf's `querySchema`.
195
195
  *
196
196
  * All but `back`/`forward` require the `Router` service (run them within the layer
@@ -198,10 +198,10 @@ function RouterLive(def, options = {}) {
198
198
  */
199
199
  /**
200
200
  * Navigates to a leaf route `ref` with typed `path`/`query` args, building the URL
201
- * via {@link href} (so it round-trips with `match`) and pushing or, with
202
- * `options.replace`, replacing the History entry. `path` is required when the
203
- * route has path params; `query` is optional when every query field is optional
204
- * (same requiredness rules as `href`).
201
+ * via {@link href} (so it round-trips with `match`) and pushing the History entry,
202
+ * or replacing it with `options.replace`. `path` is required when the route has
203
+ * path params; `query` is optional when every query field is optional (same
204
+ * requiredness rules as `href`).
205
205
  *
206
206
  * @example
207
207
  * ```ts
@@ -35,7 +35,7 @@ declare const RouterParamsError_base: Schema.Class<RouterParamsError, Schema.Tag
35
35
  }>, import("effect/Cause").YieldableError>;
36
36
  /**
37
37
  * Tagged error raised by `Router.params` / `Router.query` when the live match does
38
- * not satisfy the requested fields either no route is matched, or a requested
38
+ * not satisfy the requested fields: either no route is matched, or a requested
39
39
  * key is missing / fails its schema's `Type`-side validation. `source` records
40
40
  * whether the failure was on the path params or the query, and `keys` lists the
41
41
  * requested field names for diagnostics.
@@ -76,19 +76,16 @@ interface MatcherEntry {
76
76
  }
77
77
  /**
78
78
  * Precompiles a {@link RouterDef} into ordered matcher entries (memoized per
79
- * `RouterDef`). The patterns and path/query schemas are read from the authoritative
80
- * `def.httpApi` `"pages"` endpoints the single source of truth the server dispatch
81
- * also reads and each entry's render metadata leaf is resolved from `def.compiled`
79
+ * `RouterDef`). Patterns and path/query schemas are read from the authoritative
80
+ * `def.httpApi` `"pages"` endpoints (the single source of truth the server dispatch
81
+ * also reads), and each entry's render metadata leaf is resolved from `def.compiled`
82
82
  * by endpoint id. Matching stays local (SPA URL→leaf); see the refactor plan's
83
83
  * _Feasibility constraint_.
84
84
  *
85
85
  * Entries are sorted most-specific first (fewer params, then longer pattern) so a
86
- * static segment wins over a param segment at the same position (M6).
87
- *
88
- * Note: the specificity order is a global heuristic (param count, then length).
89
- * It resolves the common "static beats param at the same position" case, but two
90
- * patterns with the same param count and length (e.g. `/a/:b/c` vs `/a/x/:d`)
91
- * fall back to endpoint order.
86
+ * static segment wins over a param segment at the same position (M6). That order is
87
+ * a global heuristic, so two patterns with the same param count and length (e.g.
88
+ * `/a/:b/c` vs `/a/x/:d`) fall back to endpoint order.
92
89
  */
93
90
  declare function compileMatchers(def: RouterDef): readonly MatcherEntry[];
94
91
  /**
@@ -101,7 +98,7 @@ declare function match(def: RouterDef, url: string): RouteMatch;
101
98
  //#endregion
102
99
  //#region src/router-service.d.ts
103
100
  /**
104
- * The universal router service. Provided per render by `RouterLive` on the
101
+ * The universal router service. Provided per render: by `RouterLive` on the
105
102
  * client (History-API backed) and by a fixed per-request implementation on the
106
103
  * server. Layouts and pages read it anywhere via `yield* Router`.
107
104
  *
@@ -109,12 +106,12 @@ declare function match(def: RouterDef, url: string): RouteMatch;
109
106
  * {@link Router.layout}, and {@link Router.router} build the route tree (mirroring
110
107
  * `Component.gen` / `Boundary.catchTag` / `h.div`), and {@link Router.Outlet} /
111
108
  * {@link Router.params} / {@link Router.query} deliver the outlet and the live
112
- * match's params/query by dependency injection. The roles merge by declaration
109
+ * match's params/query by dependency injection. The roles merge by declaration:
113
110
  * `yield* Router` reads the service; `Router.route(…)` authors a tree.
114
111
  */
115
112
  /**
116
113
  * The platform `HttpApiClient` derived from a router's `HttpApi` spine. Typed
117
- * opaquely (`Client<any, …>`) because the spine is `HttpApi.Top` its
114
+ * opaquely (`Client<any, …>`) because the spine is `HttpApi.Top`: its
118
115
  * group/endpoint shapes are assembled in a runtime loop by `buildHttpApi`, so a
119
116
  * precise client type is not recoverable. Present (`Option.some`) on the client
120
117
  * (`RouterLive`), absent (`Option.none`) on the server, which is itself the origin.
@@ -156,8 +153,8 @@ declare const Router_base: Context.ServiceClass<Router, "@weftui/router/Router",
156
153
  /**
157
154
  * The derived {@link RouterHttpApiClient} for network work (route prefetch,
158
155
  * foundation for future loaders/data). `Option.some` on the client,
159
- * `Option.none` on the server. SPA URL→leaf resolution does **not** use this
160
- * it stays local via the shared matcher (see the refactor _Feasibility constraint_).
156
+ * `Option.none` on the server. SPA URL→leaf resolution does **not** use this.
157
+ * It stays local via the shared matcher (see the refactor _Feasibility constraint_).
161
158
  */
162
159
  readonly httpApiClient: Option.Option<RouterHttpApiClient>;
163
160
  /**
@@ -178,17 +175,17 @@ declare const OutletTag_base: Context.ServiceClass<OutletTag, "@weftui/router/Ou
178
175
  * reads it with `yield* Router.Outlet`.
179
176
  *
180
177
  * Typed **opaque** as `Node<never, never>` so splicing `[outlet]` adds nothing to
181
- * a layout's local channels the subtree's real channels are aggregated
178
+ * a layout's local channels: the subtree's real channels are aggregated
182
179
  * structurally by {@link makeLayout} / {@link makeRouter}, never inferred across
183
180
  * this DI boundary. Re-exported on the namespace as `Router.Outlet`.
184
181
  */
185
182
  declare class OutletTag extends OutletTag_base {}
186
183
  /**
187
184
  * Reads the live match's **path params** for the requested `fields`. Snapshot
188
- * semantics reads `yield* Router` then `currentMatch.get` and returns the
185
+ * semantics (reads `yield* Router`, then `currentMatch.get`), returning the
189
186
  * already-decoded values **directly**: the matcher decoded them against the leaf's
190
- * full path schema, so no re-validation is needed (the cast is sound the picked
191
- * subset is the `Type` side of `fields`). Fails with a {@link RouterParamsError}
187
+ * full path schema, so no re-validation is needed. The cast is sound because the
188
+ * picked subset is the `Type` side of `fields`. Fails with a {@link RouterParamsError}
192
189
  * (`source: "path"`) only when no route is matched. Re-exported as `Router.params`.
193
190
  */
194
191
  declare function readParams<F extends Fields>(fields: F): Effect.Effect<FieldsType<F>, RouterParamsError, Router>;
@@ -252,7 +249,7 @@ type FieldsType<F extends Fields> = Schema.Struct.Type<F>;
252
249
  * The router passes `{ path, query }` into the leaf slot at render time (the
253
250
  * {@link makeRoute} props-form overload), so a page can read them directly as props
254
251
  * instead of via the `Router.params` / `Router.query` dependency-injection
255
- * accessors. Layouts and deeper nodes which can't take handler args — keep DI.
252
+ * accessors. Layouts and deeper nodes keep DI, since they can't take handler args.
256
253
  */
257
254
  interface RouteHandlerProps<Path extends Fields = {}, Query extends Fields = {}> {
258
255
  /** The live match's decoded path params (`Type` side of the route's `path`). */
@@ -264,26 +261,26 @@ interface RouteHandlerProps<Path extends Fields = {}, Query extends Fields = {}>
264
261
  * The shape of a route/layout `component` slot: a callable producing a {@link Node},
265
262
  * invoked by the router at render time. It accepts both a plain zero-arg thunk
266
263
  * (`() => h.div(…)`) and a {@link Component} produced by `Component.make` /
267
- * `Component.gen` (a generic `(props, children?) => Node`). The `props: any` arm
268
- * rather than `()` is what keeps a required-props `Component<…>` structurally
264
+ * `Component.gen` (a generic `(props, children?) => Node`). The `props: any` arm,
265
+ * rather than `()`, is what keeps a required-props `Component<…>` structurally
269
266
  * assignable; the router calls the slot with no arguments.
270
267
  */
271
268
  type ComponentSlot<N extends Node<any, any> = Node<any, any>> = (props: any) => N;
272
269
  /**
273
270
  * The {@link Node} a {@link ComponentSlot} produces when the router invokes it with no
274
- * props/children used to recover the slot's `E`/`R` channels for the route tree.
271
+ * props/children. Used to recover the slot's `E`/`R` channels for the route tree.
275
272
  *
276
273
  * A plain zero-arg thunk is matched first (`() => infer N`): a required-props
277
274
  * `Component` is *not* assignable to `() => unknown`, so it falls through to the
278
275
  * `Component` arm, where the internal `E`/`R` type parameters are read directly. This
279
- * two-step form is deliberate `ReturnType<S>` collapses a generic `Component`'s
276
+ * two-step form is deliberate: `ReturnType<S>` collapses a generic `Component`'s
280
277
  * channels to `unknown` (they depend on the erased `GenP`/`GenC`), whereas extracting
281
278
  * the `Component<…, E, R>` parameters preserves them. Caller prop/children channels are
282
279
  * never relevant here because the router supplies neither.
283
280
  */
284
281
  type SlotNode<S> = S extends (() => infer N) ? N : S extends Component.Component<any, any, infer E, infer R> ? Node<E, R> : never;
285
282
  /**
286
- * A leaf page in the route tree. Its `component` *is* its handler a
283
+ * A leaf page in the route tree. Its `component` *is* its handler: a
287
284
  * {@link ComponentSlot} (a `Component.gen` / `Component.make` component, or a plain
288
285
  * `() => Node` thunk) that the router invokes at render time and that reads the live
289
286
  * match's params via `Router.params` / `Router.query`. `Path`/`Query` drive matching
@@ -304,7 +301,7 @@ interface RouteNode<Path extends Fields = {}, Query extends Fields = {}, E = nev
304
301
  }
305
302
  /**
306
303
  * A layout wrapping an outlet (the next level down) in the route tree. A layout is
307
- * **purely UI nesting** it owns **no path or segment**; all path structure lives
304
+ * **purely UI nesting**: it owns **no path or segment**; all path structure lives
308
305
  * on routes. Its `component` is a {@link ComponentSlot} that splices the injected
309
306
  * outlet via `yield* Router.Outlet`; the router invokes it per render and discharges
310
307
  * that `Outlet` requirement. A layout that needs a param reads it via `Router.params`.
@@ -319,7 +316,7 @@ interface LayoutNode<E = never, R = never> {
319
316
  /**
320
317
  * Phantom marker for this layout subtree's aggregate error channel (see
321
318
  * {@link TreeE}). Covariant (stores `E` directly) so a fully-discharged layout
322
- * (`LayoutNode<never, never>` its `Outlet` provided, no subtree errors) stays
319
+ * (`LayoutNode<never, never>`, its `Outlet` provided and no subtree errors) stays
323
320
  * assignable to the `LayoutNode<any, any>` arm of {@link TreeNode}.
324
321
  */
325
322
  readonly _E?: E;
@@ -337,26 +334,17 @@ type SubtreeE<C extends readonly TreeNode[]> = TreeE<C[number]>;
337
334
  /** Aggregate requirement channel over a children tuple (distributes over `C[number]`). */
338
335
  type SubtreeR<C extends readonly TreeNode[]> = TreeR<C[number]>;
339
336
  /**
340
- * Declares a leaf page. The `component` *is* the route handler a thunk the
337
+ * Declares a leaf page. The `component` *is* the route handler: a thunk the
341
338
  * router invokes at render time; its error / requirement channels propagate up the
342
339
  * tree. Two authoring forms are accepted:
343
340
  *
344
- * - **Handler-arg props** the slot declares `(props: {@link RouteHandlerProps})`
341
+ * - **Handler-arg props**: the slot declares `(props: {@link RouteHandlerProps})`
345
342
  * and the router passes the live match's decoded `{ path, query }` in directly
346
343
  * (first overload; `path`/`query` are inferred from the route's `path`/`query`
347
- * fields). A plain zero-arg thunk works too it just ignores the props.
348
- * - **Dependency injection** a `Component.make` / `Component.gen` component that
344
+ * fields). A plain zero-arg thunk works too. It just ignores the props.
345
+ * - **Dependency injection**: a `Component.make` / `Component.gen` component that
349
346
  * reads the live match via `Router.params` / `Router.query` (second overload).
350
347
  *
351
- * @example Handler-arg props (decoded `{ path, query }`)
352
- * ```ts
353
- * Router.route("users/:id", {
354
- * path: { id: Schema.NumberFromString },
355
- * query: { tab: Schema.optional(Schema.String) },
356
- * component: ({ path, query }) => h.div({}, `User ${path.id} (${query.tab ?? "info"})`),
357
- * });
358
- * ```
359
- *
360
348
  * @example Dependency injection (`Router.params` / a `Component`)
361
349
  * ```ts
362
350
  * Router.route("about", { component: Component.make(() => h.h1({}, "About")) });
@@ -404,24 +392,16 @@ declare function makeLayout<C extends readonly TreeNode[], S extends ComponentSl
404
392
  }, children: C): LayoutNode<Node.Error<SlotNode<S>> | SubtreeE<C>, Exclude<Node.Context<SlotNode<S>>, Router.Outlet> | SubtreeR<C>>;
405
393
  /**
406
394
  * Wraps a dynamic-import `load` as a lazy {@link ComponentSlot}: the route's descriptor
407
- * (`segment`, `path`/`query`) stays eager and matchable, while the component the render
408
- * body and its module's deps is split into the chunk `load` resolves. The router invokes
395
+ * (`segment`, `path`/`query`) stays eager and matchable, while the component (the render
396
+ * body and its module's deps) is split into the chunk `load` resolves. The router invokes
409
397
  * the returned slot at render time; it awaits `load` then renders the resolved component,
410
398
  * adopting the server DOM in place on hydration (flash-free) and fetching the chunk on
411
399
  * client navigation. Exposed as {@link Router.lazy}. See `lazy-component.specs.md`.
412
400
  *
413
401
  * The resolved value is a component slot (`Component.gen` / `Component.make`, or a
414
- * `() => Node` thunk) the shape `component:` already accepts so its `E`/`R` channels
402
+ * `() => Node` thunk), the shape `component:` already accepts, so its `E`/`R` channels
415
403
  * are recovered via {@link SlotNode} and propagate up the tree exactly as an eager
416
404
  * component's do.
417
- *
418
- * @example
419
- * ```ts
420
- * Router.route("docs/:category/:slug", {
421
- * path: { category: Schema.String, slug: Schema.String },
422
- * component: Router.lazy(() => import("./doc-page").then((m) => m.DocPage)),
423
- * });
424
- * ```
425
405
  */
426
406
  declare function lazyComponent<S extends ComponentSlot>(load: () => Promise<S>): () => Node<Node.Error<SlotNode<S>>, Node.Context<SlotNode<S>>>;
427
407
  //#endregion
@@ -430,7 +410,7 @@ declare function lazyComponent<S extends ComponentSlot>(load: () => Promise<S>):
430
410
  * A compiled layout level: its component slot plus the dedupe `patternPrefix` used
431
411
  * by the client outlet to key the level. A layout owns no path of its own, so the
432
412
  * prefix is derived as the **longest common path-segment prefix of every leaf in
433
- * the layout's subtree** it changes (and the level re-renders) exactly when a
413
+ * the layout's subtree**: it changes (and the level re-renders) exactly when a
434
414
  * param shared by all those leaves changes, and persists otherwise.
435
415
  */
436
416
  interface CompiledLayout {
@@ -456,7 +436,7 @@ interface CompiledLeaf {
456
436
  /**
457
437
  * Path-param schema. Its **encoded** side is typed string-encodeable
458
438
  * (`Record<string, string | undefined>`) so it satisfies platform's
459
- * `HttpApiEndpoint` `params` constraint without an `as any` cast param schemas
439
+ * `HttpApiEndpoint` `params` constraint without an `as any` cast: param schemas
460
440
  * round-trip strings, so the `Schema.Struct` value is asserted to this shape.
461
441
  */
462
442
  readonly pathSchema: Schema.Codec<Record<string, unknown>, Readonly<Record<string, string | undefined>>>;
@@ -520,9 +500,9 @@ declare const leafRegistry: WeakMap<RouteNode<any, any, any, any>, CompiledLeaf>
520
500
  * Pass 1 walks the tree: only **routes** contribute path parts (layouts own no
521
501
  * path), so each leaf's `parts` come solely from the route segments on its branch,
522
502
  * and its ancestor `LayoutNode`s are recorded in order. Pass 2 derives one shared
523
- * {@link CompiledLayout} per distinct layout node its `patternPrefix` is the
524
- * longest common path prefix of that layout's subtree leaves then assembles each
525
- * leaf's `layoutChain` (root → parent) and merged path schema.
503
+ * {@link CompiledLayout} per distinct layout node, whose `patternPrefix` is the
504
+ * longest common path prefix of that layout's subtree leaves. It then assembles
505
+ * each leaf's `layoutChain` (root → parent) and merged path schema.
526
506
  */
527
507
  declare function compile(def: {
528
508
  root: TreeNode;
@@ -530,7 +510,7 @@ declare function compile(def: {
530
510
  }): Compiled;
531
511
  /**
532
512
  * Builds the authoritative `HttpApi` for a compiled tree (S4): a single `"pages"`
533
- * group whose endpoints are GET endpoints — one per leaf at each leaf's full path
513
+ * group holding one GET endpoint per leaf, at each leaf's full path
534
514
  * pattern, carrying `params: pathSchema`, `query: querySchema`, a
535
515
  * `Schema.String` (text/HTML) success, and a `RouterNotFound → 404` error. The tree
536
516
  * (not `HttpApi`) is the authoring surface; this is the single source of truth the
@@ -1,4 +1,4 @@
1
- import { l as leafRegistry } from "./outlet-C9N4a4_F.js";
1
+ import { l as leafRegistry } from "./outlet-BgJmsO_g.js";
2
2
  import { Result, Schema } from "effect";
3
3
  //#region src/matcher.ts
4
4
  /** Escapes a literal path segment for inclusion in a `RegExp`. */
@@ -28,19 +28,16 @@ const emptySchema = Schema.Struct({});
28
28
  const matchersCache = /* @__PURE__ */ new WeakMap();
29
29
  /**
30
30
  * Precompiles a {@link RouterDef} into ordered matcher entries (memoized per
31
- * `RouterDef`). The patterns and path/query schemas are read from the authoritative
32
- * `def.httpApi` `"pages"` endpoints the single source of truth the server dispatch
33
- * also reads and each entry's render metadata leaf is resolved from `def.compiled`
31
+ * `RouterDef`). Patterns and path/query schemas are read from the authoritative
32
+ * `def.httpApi` `"pages"` endpoints (the single source of truth the server dispatch
33
+ * also reads), and each entry's render metadata leaf is resolved from `def.compiled`
34
34
  * by endpoint id. Matching stays local (SPA URL→leaf); see the refactor plan's
35
35
  * _Feasibility constraint_.
36
36
  *
37
37
  * Entries are sorted most-specific first (fewer params, then longer pattern) so a
38
- * static segment wins over a param segment at the same position (M6).
39
- *
40
- * Note: the specificity order is a global heuristic (param count, then length).
41
- * It resolves the common "static beats param at the same position" case, but two
42
- * patterns with the same param count and length (e.g. `/a/:b/c` vs `/a/x/:d`)
43
- * fall back to endpoint order.
38
+ * static segment wins over a param segment at the same position (M6). That order is
39
+ * a global heuristic, so two patterns with the same param count and length (e.g.
40
+ * `/a/:b/c` vs `/a/x/:d`) fall back to endpoint order.
44
41
  */
45
42
  function compileMatchers(def) {
46
43
  const cached = matchersCache.get(def);
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import { A as notFound, C as RouterHttpApiClient, D as RouterNotFound, E as match, O as RouterParamsError, S as Router, T as compileMatchers, _ as TreeE, a as RouterOptions, b as NavState, c as leafRegistry, d as FieldsType, f as LayoutNode, g as SubtreeR, h as SubtreeE, i as RouterDef, k as isRouterNotFound, l as ComponentSlot, m as RouteNode, n as CompiledLayout, o as buildHttpApi, p as RouteHandlerProps, r as CompiledLeaf, s as compile, t as Compiled, u as Fields, v as TreeNode, w as RouteMatch, x as NavigateOptions, y as TreeR } from "./compile-DySac7KN.js";
2
- import { i as href, n as outletNode, r as HrefArgs, t as RouterApp } from "./outlet-saKduDFz.js";
1
+ import { A as notFound, C as RouterHttpApiClient, D as RouterNotFound, E as match, O as RouterParamsError, S as Router, T as compileMatchers, _ as TreeE, a as RouterOptions, b as NavState, c as leafRegistry, d as FieldsType, f as LayoutNode, g as SubtreeR, h as SubtreeE, i as RouterDef, k as isRouterNotFound, l as ComponentSlot, m as RouteNode, n as CompiledLayout, o as buildHttpApi, p as RouteHandlerProps, r as CompiledLeaf, s as compile, t as Compiled, u as Fields, v as TreeNode, w as RouteMatch, x as NavigateOptions, y as TreeR } from "./compile-Bb7AknG_.js";
2
+ import { i as href, n as outletNode, r as HrefArgs, t as RouterApp } from "./outlet-2AnUWKQD.js";
3
3
  export { type Compiled, type CompiledLayout, type CompiledLeaf, type ComponentSlot, type Fields, type FieldsType, type HrefArgs, type LayoutNode, type NavState, type NavigateOptions, type RouteHandlerProps, type RouteMatch, type RouteNode, Router, RouterApp, type RouterDef, type RouterHttpApiClient, RouterNotFound, type RouterOptions, RouterParamsError, type SubtreeE, type SubtreeR, type TreeE, type TreeNode, type TreeR, buildHttpApi, compile, compileMatchers, href, isRouterNotFound, leafRegistry, match, notFound, outletNode };
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import { a as Router, c as compile, d as RouterParamsError, f as isRouterNotFound, l as leafRegistry, n as outletNode, p as notFound, s as buildHttpApi, t as RouterApp, u as RouterNotFound } from "./outlet-C9N4a4_F.js";
2
- import { n as compileMatchers, r as match, t as href } from "./href-uvJ6b7zz.js";
1
+ import { a as Router, c as compile, d as RouterParamsError, f as isRouterNotFound, l as leafRegistry, n as outletNode, p as notFound, s as buildHttpApi, t as RouterApp, u as RouterNotFound } from "./outlet-BgJmsO_g.js";
2
+ import { n as compileMatchers, r as match, t as href } from "./href-3swSSbn_.js";
3
3
  export { Router, RouterApp, RouterNotFound, RouterParamsError, buildHttpApi, compile, compileMatchers, href, isRouterNotFound, leafRegistry, match, notFound, outletNode };
@@ -1,4 +1,4 @@
1
- import { D as RouterNotFound, S as Router, d as FieldsType, i as RouterDef, m as RouteNode, u as Fields } from "./compile-DySac7KN.js";
1
+ import { D as RouterNotFound, S as Router, d as FieldsType, i as RouterDef, m as RouteNode, u as Fields } from "./compile-Bb7AknG_.js";
2
2
  import { Node } from "@weftui/core";
3
3
  //#region src/href.d.ts
4
4
  /**