603 Commits
Author SHA1 Message Date
Bendik Aagaard LynghaugandClaude Fable 5 9671670269 fix(ci): glob test files instead of passing directories to node --test
ci / test (24) (push) Successful in 35s
ci / test (26) (push) Successful in 31s
ci / e2e (push) Successful in 1m5s
release / publish (push) Successful in 35s
Node 24's test runner spawns directory arguments as entry modules
(MODULE_NOT_FOUND); Node 26 accepts them. Shell-expanded file globs work
on both — found by the first real CI run on the Gitea runner, where the
Node 24 matrix leg and the release gate failed while 26 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
v8.0.0
2026-09-08 21:46:42 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 c5f297a3b7 release: 8.0.0
ci / test (24) (push) Failing after 32s
ci / test (26) (push) Successful in 33s
ci / e2e (push) Failing after 1m6s
release / publish (push) Failing after 33s
All workspace packages to 8.0.0; inter-package ranges tightened from *
to ^8.0.0 for publication.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
2026-09-08 21:35:15 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 af482330e9 fix(bankai): survive real-world CSS and serve real media types
ci / test (24) (push) Failing after 1m55s
ci / test (26) (push) Successful in 35s
ci / e2e (push) Failing after 1m8s
Lightning CSS refuses IE-era hacks (*zoom and friends) that established
libraries like tachyons still carry; enable errorRecovery so bankai
strips them instead of failing the build — a zero-config tool ingesting
decade-old CSS should shrug, not crash. Found migrating an actual choo
v7 production site. Also: proper MIME types for avif/jpg/gif/mp4/woff in
the static server.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
2026-09-08 20:03:09 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 ce1ec9e4a9 rebrand: buuh — a friendly public fork under the uhhm org
Packages renamed to the @uhhm scope (@uhhm/buuh, @uhhm/buuh-html,
@uhhm/buuh-component, @uhhm/buuh-devtools, @uhhm/buuh-migrate,
@uhhm/bankai). Scoping is load-bearing twice over: npm routes registries
per scope so @uhhm/* resolves against project.uhhm.no while everything
else stays on npmjs, and it means this fork never squats upstream's
names anywhere. The codemod now migrates choo v7 apps to the @uhhm
names. README rewritten with the fork framing and full upstream credit;
the choojs RFC moves to docs/upstream-rfc-draft.md, in the drawer for if
this work ever goes home. API unchanged — choo() is still choo().

Also: Gitea Actions CI + release workflows (npm publish to the uhhm
registry on tag push, CDN bundle uploaded as a generic package),
npm run bundle producing dist-cdn/buuh.js (the whole framework as one
minified ES module for import-map use), docs/publishing.md explaining
what Gitea Packages is (a real npm registry) and is not (a CDN — serve
the bundle from a static host with module-safe MIME instead), and
onload.js constructing window.MutationObserver to match its own guard
(surfaced by smoke-testing the bundle outside a full browser).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
2026-09-08 19:55:20 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 b957b02410 docs: Phase 5 — v8 README, migration guide, deploy recipes, RFC draft
README rewritten for the v8 branch: honest size claim (7.97 kB min+gzip
for the whole framework, with the v7 '4kb' context), the zero-build
story up front, bankai v10, package map, credits to yoshuawuyts, the
choojs contributors and the pirxpilot fork line.

docs/migrating-v7-to-v8.md: the codemod path, the specifier map, and
every deliberate behavior change spelled out. docs/deploy.md: plain
Node, Docker, proxy/CDN — including the HTTP/3 answer (h3 is
infrastructure's job; bankai's contract is the 103 + Link headers that
any hints-aware edge, h3 included, propagates; server push is dead
everywhere and never existed in h3) — and web-standard runtimes.

docs/rfc.md: the draft announcement for choojs/choo — continuation
framing, pings to yoshuawuyts and pirxpilot, the npm-rights ask, API
feedback questions, and a three-week comment window with a
silence-is-consent close.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
2026-09-08 19:41:22 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 99f9da1e3b feat(bankai): Phase 4 follow-ups — css convention, live titles, prerender, HTTP/2, size budget
- style.css convention: <entry dir>/style.css is imported by the virtual
  client entry (CSS is a client concern; server code never sees it — the
  v8 answer to sheetify), extracted and hashed by Vite, stylesheet-linked
  in the head and preloaded via Early Hints. Counter example styled.
- SSR <title> from state: the server reads the first stream chunk before
  writing the head, by which point stores and the first render slice have
  run — DOMTitleChange emits land in the document title. Counter emits.
- bankai build --prerender /,/about renders routes through the same
  toStream path to static <route>/index.html (precompressed, precached
  by the service worker, served with Early Hints).
- bankai serve --h2: HTTP/2 with a generated local cert (openssl,
  cached; allowHTTP1) — browsers only act on Early Hints over h2/h3.
  Tested with a real h2 client observing the 103 interim response.
- npm run size: the framework wire-size budget, enforced in CI. Whole
  framework (core + html engine + morph + hydrate) is 7.97 kB min+gzip /
  7.15 kB brotli; budget 8.5 kB with the v7 '4kb' context documented
  (that number excluded the html engine, which lived in the browserify
  transform).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
2026-09-08 19:39:25 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 0a351d7f33 docs: Phase 4 complete — bankai v10
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
2026-09-08 19:32:38 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 8b684a17d2 feat(examples)+test(e2e): counter service worker; bankai dev+prod in Chromium
The counter example gains a sw.js exercising bankai's injected-precache
build. e2e proves both bankai paths in real Chromium: the built app
serves, hydrates and runs with zero console errors and zero mismatch
warnings (and choo consumed window.initialState), and the dev server's
Vite-transformed virtual entry hydrates the same way.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
2026-09-08 19:32:37 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 7e08b585ac feat(bankai): bankai v10 — the isomorphic compiler & server on Vite 8
One command, zero config, and only the client ever bundles: v8 server
code is plain ESM that Node runs as-authored, so there is no server
build to rot (the lesson of v9).

- start: Vite middleware mode + HMR with per-request streaming SSR via
  ssrLoadModule; a virtual client entry generates the browser glue so
  the user writes exactly one isomorphic module (plan decision D5).
- build: client bundle via Vite 8/Rolldown, manifest-derived route
  assets in dist/bankai.json, service worker built with the precache
  list injected (choo-service-worker convention, manifest edition),
  brotli+gzip precompression of every text asset.
- serve: immutable caching + precompressed negotiation for hashed
  assets, 103 Early Hints (res.writeEarlyHints) with the route's assets
  before every page, streaming SSR, and a window.initialState tail with
  script-breakout-safe serialization and choo internals filtered out.
- inspect: raw/gzip/brotli size report.

Integration tests drive the real counter example through build and
serve, asserting the 103 interim response at the HTTP level.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
2026-09-08 19:32:37 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 d755af5ca0 fix(html): hydration mismatch detection ignores <script> elements
Server pages legitimately carry scripts (state serialization, analytics)
that are spent by hydration time and that client views never render —
they are noise to the detector, and morph dropping them is harmless.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
2026-09-08 19:32:37 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 ce8fedee0b docs: Phase 3 complete — toStream, prefetch, lazy routes, devtools, codemod
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
2026-09-08 18:57:51 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 c101ee464a feat(migrate): add @choojs/migrate — the choo-migrate v7→v8 codemod
Regex-based on purpose: converts simple top-level CJS patterns to ESM,
remaps package specifiers to their @choojs homes, points retired nano*
packages at built-in replacements (choo-lazy-route → lazy()), and reports
everything it refuses to guess at instead of guessing. Validated against
choo's own v7 example app: migrated output runs on v8 unmodified.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
2026-09-08 18:57:51 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 fd1d05cea0 feat(devtools): add @choojs/devtools — window.choo console tooling
Carries forward the choo-devtools 3.x essentials: live state handle,
emit-from-console, event log, nanotiming measures via PerformanceObserver,
state copy(), help(). No-op on the server; verbose logging opt-in via
localStorage.CHOO_DEVTOOLS_VERBOSE.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
2026-09-08 18:57:51 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 d22f996596 feat(examples)+test(e2e): streaming SSR demo, proven progressive in Chromium
examples/streaming is a runnable Node server piping toStream() through
Readable.fromWeb. The e2e server gains /stream, and two tests prove real
streaming: shell bytes arrive over HTTP before the async hole resolves,
and Chromium builds the shell DOM mid-stream, then grows the slow section
into the same document without navigation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
2026-09-08 18:44:52 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 22c5045c25 feat(core): toStream() and lazy() routes — the answer to choojs/choo#653
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
2026-09-08 18:43:40 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 9cf9e51ac9 feat(html): streamable server templates — async holes in child position
The server tag now builds a parts list (string runs + async holes) instead
of an eager string. Sync templates collapse to one string and toString()
is byte-identical to before. Promises and async iterables in child
position make the result streamable: iterating it yields everything before
a hole immediately, then each hole as it resolves, in document order —
resolved values get full child semantics (templates, arrays, raw,
escaping). toString() on async content throws with streaming guidance;
async values in attribute position throw immediately.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
2026-09-08 18:43:40 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 c9f5ba2fda bench: @choojs/html vs nanohtml v1 vs µhtml v5, happy-dom + real Chromium
npm run bench (happy-dom, all three engines) and npm run bench:browser
(Playwright Chromium, the two ESM engines). Honest numbers recorded in
docs/v8.md: we create ~12% faster than µhtml in real Chromium (the
parse-once/clone design), µhtml updates in place ~5x faster than
fresh-tree + nanomorph (choo's architectural cost, mitigated by component
caching), and server strings are on par with nanohtml v1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
2026-09-08 17:59:53 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 63b56144a5 test(e2e): real-browser pass in Playwright Chromium
Three end-to-end scenarios against a static+SSR test server: the
zero-build page (import map + native ESM, no bundler), the SSR page
(rendered content in the raw response, live after hydration, no mismatch
warnings), and adoption (an expando on the server-rendered node survives
a real render). Renders are raf-batched so assertions poll.

Real Chromium flushed out three fixes happy-dom couldn't see:
- nanoraf called an extracted requestAnimationFrame bare — Illegal
  invocation under strict-mode ESM (sloppy CJS had masked it); wrapped.
- the counter example only routed '/', so serving it from any subpath
  threw; it now has a wildcard fallback.
- hydration mismatch detection is now whitespace-insensitive (the parser
  reparents whitespace, e.g. text after </body>), and page scripts belong
  in <head> when a view owns <body> — same convention bankai v9 used.

CI gets an e2e job with chromium-headless-shell.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
2026-09-08 17:57:27 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 13155a3469 feat(html): adoption-style hydration with mismatch reporting
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
2026-09-08 17:49:32 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 37bd8adf81 docs: record the async-route (choo#653) design as a Phase 3 deliverable
Deliberately deferred so the SSR half (toStream) ships together with the
browser half — the missing server story is what stalled the original PR.
API shape goes to the RFC.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
2026-09-08 17:44:13 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 870aa79668 test+docs: full-app browser integration test and the isomorphic counter example
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
2026-09-08 16:56:54 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 c696e00d4f feat(component): add @choojs/component — nanocomponent + on-load as ES classes
Ported from nanocomponent 6.6.0 and on-load 3.4.1 (MIT) with attribution:
render/update/morph lifecycle, proxy nodes with isSameNode identity, and
the MutationObserver-driven load/unload hooks. This class is the intended
island/hydration boundary for later phases. @choojs/core exposes ./timing
as a subpath for the instrumentation calls.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
2026-09-08 16:56:54 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 84230b1770 feat(html): browser renderer — runtime-cached tagged templates
Each template literal parses once, keyed by its strings array in a
WeakMap: static parts become a <template> with hole markers, renders
clone and fill. Interpolated values never pass through innerHTML (child
values become text nodes or adopted DOM; attributes go through
setAttribute). Event handlers set as properties so nanomorph copies them.
Document-level roots (<body>/<head>/<html>) parse via DOMParser because
<template> drops them. Replaces the v7 browserify transform entirely —
production speed is now a runtime property.

Tests run in happy-dom (new devDependency), including a check that server
and browser renderers serialize identical markup.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
2026-09-08 16:56:53 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 501e8f5927 fix(core): URL normalization that survives non-ASCII and malformed input
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
2026-09-08 16:50:34 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 4ad3d02207 feat(core): add @choojs/core — ESM port of choo with consolidated nano* internals
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
2026-09-08 15:59:26 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 90cca611c9 feat(html): add @choojs/html — server renderer, raw(), morph
ESM ports with attribution: nanohtml 1.10.0 server tag (browserify/babel
transform branches removed — pure runtime), raw-server, and nanomorph 5.4.3
consolidated into one module. browser.js is a Phase 2 placeholder that
throws loudly. Covered by test/server.test.js on node:test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
2026-09-08 15:59:26 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 3d6b474621 chore: scaffold v8 monorepo — npm workspaces, node:test, GitHub Actions CI
The v7 source at the repo root is untouched; v8 work lives in packages/.
See docs/v8.md for layout and status against the modernization plan.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
2026-09-08 15:59:26 +02:00
slegarraga 3e5988fba5 fix: correct typo 'overriden' → 'overridden' in README (#718) 2026-02-20 10:15:19 +00:00
Renée Kooi 276b07043c 7.1.0 2020-01-23 18:13:33 +01:00
Renée Kooi 2893de86d0 Type improvements (#706)
* use nanobus for the emitter typings

instead of `events`, which is a bit different

* add typings for `cache` option
2020-01-23 18:11:06 +01:00
Sean Genabe 2d56f305e0 typings: add IChoo.hash (#691) 2020-01-23 16:30:43 +01:00
Will Heslam efb131c2ae Update README.md (#705)
Remove apostrophe from possessive "its"
2019-10-03 12:07:00 +02:00
Bret Comnes 75c6ed6d00 Fix travis stuff 2019-08-14 10:48:33 +02:00
Carl Törnqvist 11aa89d626 7.0.0 2019-08-05 15:46:42 +02:00
Carl Törnqvist 1323fe9bae Add changelog heading 2019-08-05 15:44:06 +02:00
Carl Törnqvist c153d180e3 Update changelog 2019-08-05 15:40:02 +02:00
Carl Törnqvist 60ed830828 7.0.0-0 2019-06-12 17:25:04 +02:00
Carl Törnqvist 842b7aaa5c Immutable state on SSR (toString) (#649)
* Init stores with state

* Reset state.components on toString
2019-06-12 15:13:33 +02:00
Carl Törnqvist a871818d32 Match route before init stores (#698) 2019-06-12 13:42:18 +02:00
Yoshua Wuyts b373c7ee06 more timings (#578)
* more timings

* Remove router timing
2019-06-12 13:41:09 +02:00
Carl Törnqvist d62691f207 Disable hash routing by default (#699) 2019-06-11 23:05:15 +02:00
Carl Törnqvist 0b4d578f01 Update nanorouter (#701) 2019-06-11 22:51:03 +02:00
alex 555ab4b5f9 Use Object.assign instead of xtend (#616)
As described by #563:

> https://polyfill.io provides a fallback for browsers that don't support it, so for most people this will be a strict improvement.
2019-06-11 22:50:34 +02:00
Carl Törnqvist 3d3b2280f4 6.13.3 2019-04-03 10:53:30 +02:00
Carl Törnqvist 4c15924308 Revert init order change 2019-04-03 10:51:41 +02:00
Carl Törnqvist a22fca69de Add browser tests (#696) 2019-04-02 14:55:21 +02:00
Carl Törnqvist 5879bdb404 6.13.2 2019-04-02 09:28:02 +02:00
Carl Törnqvist 6a2f14c292 Fix location missing on store init (#695) 2019-04-02 09:27:06 +02:00
Kiril 5921d27648 Typo Fix in Readme (#693)
The component class should be Map but not Button in the example.
2019-02-12 23:34:36 +01:00
Renée Kooi b9bb656b4c 6.13.1 2018-10-30 12:06:25 +01:00