@xmachines/play-svelte-demo
Examples / @xmachines/play-svelte-demo
Svelte 5 renderer demo for @xmachines/play-svelte — actor + PlayRenderer without a router.
What This Demonstrates
- Shared auth machine reused without framework-specific business logic
PlayRendererrendering actor-projected views with a typeddefineRegistrycatalog- Auth machine states drive view switching via
auth.login/auth.logoutevents only — no URL routing - Svelte 5 runes and
mountAPI mapped to TC39 Signals lifecycle - Non-browser invariant tests plus browser renderer coverage
Running the Demo
From the repository root:
npm installnpm run dev -w @xmachines/play-svelte-demoThen open http://localhost:5173.
Step-by-Step Code Flow
Use this order to understand the implementation:
src/main.tscallsdefinePlayer({ machine: authMachine })to create a factory, invokes it to get an actor, starts it, and mountsApp.sveltevia Svelte’smount.- The actor is passed to
Appas a prop. src/App.sveltecallsdefineRegistry(authCatalog, { components, actions })to build the typedregistryResult— real async action handlers dispatching toactor.send().App.sveltedelegates toShell.svelte, passing bothactorandregistryResult.Shell.svelterenders<PlayUIProvider><PlayRenderer /></PlayUIProvider>,<NavBar>, and<DebugPanel>— the latter two observe actor signals directly viawatchSignalinside$effect.- HMR cleanup calls
actor.stop()viaimport.meta.hot.dispose. - Browser tests in
test/browser/validate startup and interaction behavior.
// src/main.ts (shape)const createPlayer = definePlayer({ machine: authMachine });const actor = createPlayer();actor.start();
mount(App, { target: document.getElementById("app")!, props: { actor } });<!-- src/App.svelte (shape) --><script lang="ts"> let { actor }: { actor: AuthActor } = $props();
const registryResult = defineRegistry(authCatalog, { components: { Home, About, Contact, Login, Dashboard, Overview, Stats, Profile, Settings, Navigation, NavBar: NavBarView, }, actions: { login: async (args) => actor.send({ type: "auth.login", username: assertNonNullable(args, "args").username }), logout: async () => actor.send({ type: "auth.logout" }), route: async (args) => { const { to, params } = assertNonNullable(args, "args"); actor.send({ type: "play.route", to, ...(params != null && { params }) }); }, }, });</script>
<Shell {actor} {registryResult} />Key Files
src/main.ts- actor creation/start and Svelte app mount with actor as propsrc/App.svelte- registry construction, delegates rendering toShell.sveltesrc/components/Shell.svelte- shared shell withPlayUIProvider+PlayRenderer,NavBar, andDebugPanelsrc/components/- demo view Svelte components bound to catalog component keystest/library-pattern.test.ts- architecture boundary and invariant assertionstest/browser/renderer-demo.browser.test.ts- browser-mode renderer coverage
State Machine & Architecture Details
The demo utilizes XMachines architectural invariants:
- Actor Authority: View components dispatch
auth.loginandauth.logoutevents to the actor. The actor evaluates guards and transitions — Svelte never decides which view to render. - Passive Infrastructure:
PlayRendererobservesactor.currentViewsignals only. It holds no business state and makes no routing decisions. - Signal-Only Reactivity: The bridge leverages Svelte 5 rune reactivity internally to react precisely when actor signals update, without ad-hoc
$statestores for business logic.
Watcher Lifecycle and Cleanup Contract
This demo follows the canonical watcher lifecycle used across all @xmachines framework adapters:
notifyqueueMicrotaskgetPending()- Read actor signals and project Svelte-local render state
- Re-arm with
watch()/watch(...signals)
Watcher notifications are one-shot. Cleanup is explicit: actor.stop() is called during HMR disposal, and PlayRenderer handles internal watcher teardown natively on component destruction.
Adapter Boundaries
PlayRenderer and defineRegistry stay passive infrastructure. Business validity remains actor-owned. The registry maps catalog component keys to Svelte 5 component implementations — the actor owns which key is active. The Shell.svelte component is shared with all Svelte router demos, accepting an optional router prop for prop-shape parity.
Available Scripts
These commands are defined in package.json:
| Command | Description |
|---|---|
npm run dev -w @xmachines/play-svelte-demo | Start Vite dev server |
npm run build -w @xmachines/play-svelte-demo | Build production bundle |
npm run preview -w @xmachines/play-svelte-demo | Preview built bundle |
npm run test -w @xmachines/play-svelte-demo | Run Vitest test suite |
npm run test:browser -w @xmachines/play-svelte-demo | Run browser-focused Vitest suite |
Verification
Use these checks to validate README claims against the current demo implementation:
npm run test -w @xmachines/play-svelte-demonpm run test:browser -w @xmachines/play-svelte-demoExpected result: library-pattern invariant tests pass and the browser renderer suite completes.
Learn More
@xmachines/play-svelte— the Svelte 5 renderer package this demo exercises