Cross-package demo apps: each one composes two or more wcstack packages (or
showcases a repo-level concern like SSR or framework interop). Focused
single-package demos live in that package's own examples/ directory instead:
packages/fetch/examples/—pagination(5-stack comparison),users-crud,infinite-scrollpackages/speech/examples/—speech-echo,speak-highlightpackages/defined/examples/—defined-loaderpackages/midi/examples/—midi-faderpackages/view-transition/examples/—list-transitionspackages/state/examples/— binding basics
Every demo is buildless and loads packages straight from the CDN
(https://esm.run/@wcstack/<pkg>/auto one-liners; signals demos import the
single @wcstack/signals/dom entry) — except the React/Vue variants of
websocket-chat, which use Vite.
Script order: list the I/O-node packages before @wcstack/state. Module
scripts execute in document order, so this guarantees every custom element is
defined before state attaches its bindings — and the guarantee is worth more
than tidiness:
- A property / spread binding on a not-yet-defined element is deferred via
customElements.whenDefinedand re-applied with the latest value once the class arrives, so nothing is lost there. - A command-token emit is not replayed. Its subscription is deferred the same
way, so if state is wired first and a slow (or failed) CDN fetch leaves an I/O
element undefined, a click that fires
$command.x.emit()inside that window reaches zero subscribers and is silently dropped.
Loading state last closes that window: nothing is wired to the UI until every
element is defined. When you cannot control the order — a snippet pasted into a
larger page, or tags arriving through @wcstack/autoloader — gate the control
that emits with @wcstack/defined: bind
<wcs-defined>'s pending to a disabled: and the button cannot be clicked
before the subscriptions exist. Prefer it over awaiting
customElements.whenDefined() inside the handler, which never rejects for a tag
that is never registered and so parks the handler forever on a failed import.
See state-tilt-maze/.
| Demo | Combines | Run | URL |
|---|---|---|---|
websocket-chat/ |
websocket × 5 stacks (vanilla / state / signals / React / Vue) | see its README | :3300–:3302, :3304–:3305 |
router-spa/ |
router + fetch + state (SPA catalog: URL ⇄ state bridge) | node examples/router-spa/server.js |
:3000 |
router-i18n/ |
router + state (i18n: locale in the basename, dictionary as an ES module) | node examples/router-i18n/server.js |
:3000 |
state-camera-record-upload/ |
camera + permission + upload + state | any static server (secure context) | — |
state-color-palette/ |
eyedropper + clipboard + storage + state | any static server (EyeDropper is Chromium-only) | — |
state-cross-tab-todo/ |
storage + broadcast + state | node examples/state-cross-tab-todo/server.js (open 2 tabs) |
:3000 |
state-custom-states/ |
fetch + websocket + state (:state() showcase) |
node examples/state-custom-states/server.js (needs the websocket-chat shared install) |
:3303 |
state-devtools-playground/ |
devtools + state + timer (overlay inspector demo) | any static server | — |
state-intersect-scroll/ |
intersection + state ($streams switchMap restart + bounded retry, $scan feed accumulation) |
node examples/state-intersect-scroll/server.js |
:3000 |
state-notification-chat/ |
notification + permission + state | any static server | — |
state-permission-banner/ |
geolocation + permission + state | any static server | — |
state-pomodoro/ |
timer + wakelock + notification + state | any static server (secure context) | — |
state-search/ |
fetch + debounce + state | node examples/state-search/server.js |
:3000 |
state-sse-dashboard/ |
sse + state ($streams) + network — one feed, two idioms |
node examples/state-sse-dashboard/server.js |
:3000 |
state-testing-todo/ |
testing + state (a todo page and its headless vitest suite) | cd examples/state-testing-todo && npm install && npm test |
— |
state-tilt-maze/ |
tilt + accelerometer + raf + wakelock + defined + state (sensor game) | any static server (secure context) | — |
signals-live-search/ |
signals + fetch | node examples/signals-live-search/server.js |
:3000 |
signals-tilt-maze/ |
signals × the same 4 sensor nodes as state-tilt-maze (core swap comparison) |
any static server (secure context) | — |
ssr/ |
@wcstack/server (SSR + hydration) | cd examples/ssr && npm install && node server.js |
:3001 |
synth-playground/ |
🎹 audio + midi + state (a modular synth written as markup) | any static server | — |
Demos marked "any static server" have no backend at all — any way of serving
the directory over http://localhost works, e.g.:
npx serve examples/state-permission-bannershared/server.js is the static-file + JSON API core the demos above delegate
to — each demo's server.js stays a thin file declaring only its own routes.
When copying a single demo out of this repo, copy examples/shared/ alongside
it. (websocket-chat/ is the exception: it ships its own self-contained server
under websocket-chat/shared/ because it needs the ws dependency, so that
scenario is portable as one directory.)
Most demo servers default to port 3000 (override with PORT=…), so run them
one at a time — or use the e2e static server (cd e2e && npm run serve), which
serves the whole repo with mocked APIs at http://127.0.0.1:4173.