* feat(webserver): add resource routes and in-process readResource
A webserver route or the fallback seat may now answer through a resource,
a function from a Fetch Request to a Response, besides the existing
node:http handler. register accepts either route form and registerFallback
accepts a handler or { resource }; existing handler callers are unchanged.
HTTP GET and HEAD requests reach a resource; webServer.readResource(request,
peer) follows the same match order and reaches only resources, so handler
routes stay unreachable to in-process readers.
client-modules registers /plugins as the fetchBundle resource, and
frontend-static claims the fallback seat with a resource that renders the
index for a supplied Peer, or runs Connection's existing authorizeIndex for
an HTTP request and renders for the browser Peer.
* fix(webserver): flush resource headers and keep one drain listener per response
Resource responses send their status and headers before the first body
chunk, register one drain/close listener pair per response so the gzip
middleware's zlib stream does not accumulate listeners across backpressure
waits, and readResource drops the body of a HEAD read as HTTP does. HTTP
requests and readResource share one responder lookup.
* chore(scripts): prune the retired client-modules unknown-cast baseline entry
* feat(frontend-static): let browsers revalidate dist files except the index
Every dist file other than the rendered index now carries
cache-control: no-cache and a weak ETag from a SHA-1 of its bytes; a
request whose If-None-Match equals that ETag receives an empty 304. The
index keeps sending no cache headers. In-process reads through
webServer.readResource revalidate the same way.
* fix(webserver): stop reading Host for resource URLs and copy no held body
A resource request's URL is the request target resolved against the same
placeholder origin the route match uses, so a missing or unparsable Host no
longer turns a dist or /plugins request into 400. The webserver ends a
resource response itself and treats any earlier close as an abort, which
also covers the Web Worker tunnel marking an aborted response ended first.
client-modules bundle bodies and frontend-static file bodies are the held
bytes themselves instead of copies, and frontend-static no longer exports
its internal serveStatic helper.
* docs(webserver): document resource route types and the handler-or-resource fallback
Paste WebResource and WebResourceRoute beside WebRoute on the web-server
subsystem page and link the catalog signatures to it, replace the remaining
'fallback handler' wording now that the seat takes a handler or a resource,
and note why the drain listener removal is a no-op under gzip.
The packer images the web template bundles, every optional bundle, and an installation manifest instead of a dumped composition, and the Worker boots that profile through app-boot like dsh web. Package-local lazy client chunks move into content-addressed archives that the VFS fetches on first asynchronous read; client-modules reads chunks asynchronously.
The Worker replaces node-addon-require-builtin with Node v24.18.0 internal-module interfaces and reports Node 24, so plugin metadata, the Cordis Loader, and HMR reach one cascaded loader backed by the Worker loader. Resolution failures carry Node error codes, the empty load cache lets HMR apply profile changes, and every optional bundle switches on and off live. Platform stubs throw ERR_FEATURE_UNAVAILABLE_ON_PLATFORM.
Replace per-plugin tapIndex regex edits with pure-data IndexInjection rows
collected fresh per render over one webserver/index-inject event. One table,
two renderers: the served form renders rows into index.html; a static worker
form ships the same rows over its boot payload. tapIndex survives as the
raw-HTML escape hatch, applied after row rendering; client-modules and
ui-theme move to the event, and the manifest global renders as
globalThis["__DSH_BOOT__"].
The client boot chain gains the seams a pre-injected transport needs: the
module loader takes loadBundle from the transport global by default, HTTP
prefetch stands down when a transport owns bundle bytes, the web-app bundle
can decline frontend serving, the gateway client installs a namespace's
whole method group inside its fiber apply so a parked dependent never
observes the service without its methods, and the dynamic-code precheck
gates through new Function so hosts without a real node:vm keep the
define-time parse gate.
A test file under packages/client now says which face it covers:
`*.client.spec.{ts,tsx}` and its `*.client.{ts,tsx}` helpers belong to the
Client aggregate, `*.host.spec.ts` to the host aggregate. The carrier's four
node-half specs take the Host suffix.
The two suffixes are mutually exclusive, so each aggregate excludes the
other's and both keep one broad test glob: `exclude` wins over `include`, and
`packages/client/**` no longer has to be excluded wholesale from the host
program with per-file `files` entries carved back out of it. A Host-face spec
that reaches only Host source therefore needs no cross-face project
reference, which the split-project rule rejects.
vitest still discovers every file through `**/*.spec.{ts,tsx}`.