toStream(location, state) renders to a web-standard ReadableStream of
UTF-8 bytes: pass it to new Response() on web servers or
Readable.fromWeb().pipe(res) on Node. It awaits store prefetch promises
(state.prefetch, the pattern the #653 thread wanted standardized) and
lazy route views before rendering, then flushes template output
progressively through async holes.
lazy(loader, loadingView?) wraps a dynamic import as a route handler:
loads once, caches forever, renders the loading view (or holds the
current tree via a placeholder) while in flight, emits render on arrival.
toString() refuses lazy routes and prefetching stores with pointers to
toStream() — both halves of the async-route story ship together, unlike
the original PR.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
hydrate(oldNode, newNode) normalizes the client tree to parser text-node
granularity, walks both trees to report the first server/client markup
disagreement (node, text, attribute, or child count, with a path), then
morphs — matching nodes are adopted in place, the client render wins.
choo.mount() now hydrates and console.warns on mismatch.
Building this surfaced two real isomorphism divergences, both fixed:
adjacent text nodes from template holes vs the parser's merged runs
(folded via Node.normalize), and the server serializing onclick="" where
the browser sets a property — event handlers now leave no trace in server
markup at all.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
The integration test runs the counter through the real browser path in
happy-dom: start(), store events, raf-batched morph, DOM click handlers,
emit coalescing. examples/counter is the Phase 2 exit criterion: one app
module mounted zero-build in a browser via import map and string-rendered
by node examples/counter/render.js.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
Replace nanorouter's regex+decodeURI normalization with the WHATWG URL
parser. Path segments are percent-decoded exactly once in the trie, with
a keep-raw fallback instead of the URIError that crashed v7 on a literal
'%'. Route definitions and locations are NFC-normalized so 'café' matches
regardless of composition. Hash-to-slash rewriting now handles every hash,
not just the first, and state.href is decoded for humans (raw on failure).
Behavior changes from v7: params are no longer double-decoded (%2540 →
'%40', not '@'), and unmatchable encodings fall back to raw segments
instead of silently 404ing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
Same public API as choo 7.1.0 (use/route/start/mount/toString/emit; choo()
callable with or without new, Choo exported). nanobus, nanorouter+wayfarer,
nanolru+component cache, nanoraf, nanohref, nanotiming, document-ready and
scroll-to-anchor are ported into lib/ with per-file attribution; nanoquery
is replaced by URLSearchParams. The v7 node test suite is ported from tape
to node:test and passes unchanged in behavior.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd