@weftui/dom 0.27.1 → 1.0.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 CHANGED
@@ -9,11 +9,9 @@ Two entry points: `@weftui/dom/client` for the browser, `@weftui/dom/server` for
9
9
  ## Installation
10
10
 
11
11
  ```bash
12
- npm install @weftui/core @weftui/dom effect@beta
12
+ npm install @weftui/core @weftui/dom effect
13
13
  ```
14
14
 
15
- Weft tracks Effect 4's beta line. This release is built and tested against `effect@4.0.0-beta.98`; the peer range accepts newer 4.0 betas, which may contain upstream breaking changes.
16
-
17
15
  `effect` is a peer dependency; `@weftui/core` is required to author the tree.
18
16
 
19
17
  ## Key exports
@@ -38,9 +38,9 @@ The channel algebra is the whole reason they exist: `catchTag("Foo", …)` remov
38
38
 
39
39
  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, and it can still fail later: a `Stream` backing a `Boundary.rpc` resource might raise `RouterNotFound` after a client-side navigation, for instance. If a `BoundaryContext` encloses the region, the failure routes to it exactly as above, and the boundary's fallback swaps in.
40
40
 
41
- 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. When that exit is a failure whose cause is not interruption-only, Weft reports it explicitly via `Effect.logError(exit.cause)`, 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.
41
+ 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 the subscription fiber's failure exit is left **unobserved**. The Effect runtime itself then reports it `"Fiber terminated with an unhandled error"` because Weft raises that fiber's `FiberRef.unhandledErrorLogLevel` from the ambient default (`Debug`) to `LogLevel.Error` and annotates the log with `weft.region`, identifying 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. Interruption — the ordinary case of unmount tearing down the region's scope — is never reported; only genuine failures are.
42
42
 
43
- This is deliberate: rather than leaving the failure to whatever the Effect runtime would otherwise do with an unobserved fiber exit, Weft observes and logs it itself, so visibility is 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. 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.
43
+ This is deliberate: rather than a Weft-specific error-reporting config, visibility is controlled by the same knobs any Effect program uses — `Logger.withMinimumLogLevel` to filter it, `Effect.withUnhandledErrorLogLevel` to change how loudly (or quietly) unhandled fiber exits are reported elsewhere in your program. 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
44
 
45
45
  ## Suspense boundaries
46
46
 
@@ -29,7 +29,7 @@ Services flow **down** from that provide point to every reader, including across
29
29
 
30
30
  ## Layer lifetime at the mount
31
31
 
32
- The `ThemeServiceLive` example above works because `mount`'s effect and the service's lifetime coincide by accident: `ThemeServiceLive` is a plain value layer with nothing to release, so it makes no difference whether it is "alive" for one tick or the whole session. That accident stops holding the moment the layer is **scoped** — built from an `acquireRelease`-backed `Layer.effect` — because `mount`'s effect resolves right after the tree's **initial render**, not when the app stops running. Streams, event handlers, and forked work all keep running on the mount's runtime long after that Effect has settled.
32
+ The `ThemeServiceLive` example above works because `mount`'s effect and the service's lifetime coincide by accident: `ThemeServiceLive` is a plain value layer with nothing to release, so it makes no difference whether it is "alive" for one tick or the whole session. That accident stops holding the moment the layer is **scoped** — built with `Layer.scoped`, backed by an `acquireRelease` — because `mount`'s effect resolves right after the tree's **initial render**, not when the app stops running. Streams, event handlers, and forked work all keep running on the mount's runtime long after that Effect has settled.
33
33
 
34
34
  `Effect.provide(scopedLayer)` is `acquireUseRelease` sugar: acquire, run the wrapped effect, then release **when that effect completes**. Wrap it directly around `mount`, and the release runs at mount-resolve — while the mounted tree is still reading from the now-disposed service:
35
35
 
@@ -280,7 +280,7 @@ export const handler = RouterServer.toWebHandler(App, { document: documentShell
280
280
 
281
281
  ### `effect/unstable/httpapi` is the spine
282
282
 
283
- The tree is the authoring surface, but `effect/unstable/httpapi`'s `HttpApi` is the **single source of truth** for paths and schemas. Sealing the tree with `Router.router(...)` builds it once (`buildHttpApi`) and stamps it onto `def.httpApi`: a single `"pages"` group with one GET endpoint per leaf at its full path pattern, carrying `params: pathSchema`, `query: querySchema`, and a `RouterNotFound → 404` error. Both sides read that one definition, so they always agree:
283
+ The tree is the authoring surface, but `effect/unstable/httpapi`'s `HttpApi` is the **single source of truth** for paths and schemas. Sealing the tree with `Router.router(...)` builds it once (`buildHttpApi`) and stamps it onto `def.httpApi`: a single `"pages"` group with one GET endpoint per leaf at its full path pattern, carrying `setPath(pathSchema)`, `setUrlParams(querySchema)`, and a `RouterNotFound → 404` error. Both sides read that one definition, so they always agree:
284
284
 
285
285
  - **Server** — `RouterServer` dispatches through `HttpApiBuilder` (platform owns request→leaf matching, path/query decode, and the 404 status).
286
286
  - **Client** — `RouterLive` derives a real `HttpApiClient` from the same `def.httpApi` (exposed as `Router.httpApiClient`) for network work. SPA URL→leaf resolution stays **local** (there is no public client-side "match this URL against my `HttpApi`" utility in platform), fed from the same endpoint definitions so it never drifts from the server.
@@ -9,7 +9,7 @@ description: Provide plain and scoped Layers to a mounted app — the direct mou
9
9
 
10
10
  **Goal:** provide a `Layer` to the mounted app so its components can read services with `yield* Service`.
11
11
 
12
- Which recipe to reach for depends on whether the layer has anything to release. Both plain and scoped layers are built with `Layer.succeed` or `Layer.effect` — the distinction is whether the layer's effect acquires releasable resources (`acquireRelease`, or a scope finalizer added directly). A plain value layer (`Layer.succeed`, or `Layer.effect` whose effect has no `acquireRelease`) can be provided directly at the mount — there is nothing to leak. A **scoped** layer (`Layer.effect` whose effect is backed by `acquireRelease`) needs the mount to outlive the effect's own resolution — see [Layer lifetime at the mount](https://weftui.dev/docs/explanation/services-and-context#layer-lifetime-at-the-mount) for why.
12
+ Which recipe to reach for depends on whether the layer has anything to release. A plain value layer (`Layer.succeed`, `Layer.effect` with no `acquireRelease`) can be provided directly at the mount — there is nothing to leak. A **scoped** layer (`Layer.scoped`, anything backed by `acquireRelease`) needs the mount to outlive the effect's own resolution — see [Layer lifetime at the mount](https://weftui.dev/docs/explanation/services-and-context#layer-lifetime-at-the-mount) for why.
13
13
 
14
14
  ## Recipe 1 — plain value layers with `mount`
15
15
 
@@ -323,7 +323,7 @@ These power the runtime and are exported for tooling/tests; most apps never touc
323
323
  | Export | Description |
324
324
  | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
325
325
  | `compile(def)` | Walks a tree into flat `CompiledLeaf`s with merged path/query schemas and layout chains. |
326
- | `buildHttpApi(leaves)` | Builds the authoritative `HttpApi` (one `"pages"` group, a GET endpoint per leaf with `params`/`query` schemas + 404). Called by `Router.router`; the result is `def.httpApi`. |
326
+ | `buildHttpApi(leaves)` | Builds the authoritative `HttpApi` (one `"pages"` group, a GET endpoint per leaf with `setPath`/`setUrlParams` + 404). Called by `Router.router`; the result is `def.httpApi`. |
327
327
  | `leafRegistry` | `WeakMap<RouteNode, CompiledLeaf>` read by `href` to resolve a leaf's pattern/schemas. |
328
328
  | `match(compiled, url)` | Resolves a URL to a `RouteMatch` (`Matched` with decoded `path`/`query`, or `NotFound`). |
329
329
  | `compileMatchers(compiled)` | Precompiles per-leaf regex matchers. |
@@ -14,11 +14,9 @@ We assume you know [Effect](https://effect.website/docs/getting-started/introduc
14
14
  ## Install
15
15
 
16
16
  ```bash
17
- npm install @weftui/core @weftui/dom effect@beta
17
+ npm install @weftui/core @weftui/dom effect
18
18
  ```
19
19
 
20
- Weft tracks Effect 4's beta line. This release is built and tested against `effect@4.0.0-beta.98`; the peer range accepts newer 4.0 betas, which may contain upstream breaking changes.
21
-
22
20
  `@weftui/core` gives you the element builders and combinators; `@weftui/dom` renders them (its `./client` entry mounts in the browser). `effect` is the peer everything is built on.
23
21
 
24
22
  ## Build a component
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weftui/dom",
3
- "version": "0.27.1",
3
+ "version": "1.0.0",
4
4
  "description": "DOM renderer for Weft — mount and hydrate in the browser, render to string or stream on the server",
5
5
  "keywords": [
6
6
  "dom",
@@ -44,12 +44,12 @@
44
44
  "access": "public"
45
45
  },
46
46
  "dependencies": {
47
- "@weftui/core": "0.27.1"
47
+ "@weftui/core": "1.0.0"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@types/jsdom": "^28.0.3",
51
51
  "@types/node": "^25.9.2",
52
- "effect": "4.0.0-beta.98",
52
+ "effect": "4.0.0-beta.93",
53
53
  "jsdom": "^29.1.1",
54
54
  "tsx": "^4.22.4",
55
55
  "typescript": "^6.0.3",
@@ -57,6 +57,6 @@
57
57
  "vite-plus": "0.2.2"
58
58
  },
59
59
  "peerDependencies": {
60
- "effect": ">=4.0.0-beta.98 <4.0.0"
60
+ "effect": "4.0.0-beta.93"
61
61
  }
62
62
  }