@xmachines/play-solid-demo
Examples / @xmachines/play-solid-demo
Solid renderer demo for @xmachines/play-solid — 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 - Canonical TC39 Signals lifecycle integrated with Solid’s fine-grained reactivity
- Non-browser invariant tests plus browser renderer coverage
Running the Demo
From the repository root:
npm installnpm run dev -w @xmachines/play-solid-demoThen open http://localhost:5173.
Step-by-Step Code Flow
Use this order to understand the implementation:
src/main.tsxmounts<App />to the DOM root via Solid’srender.src/App.tsxcreates the actor at module scope viadefinePlayer({ machine: authMachine })then calls the returned factory to produce the actor and starts it.defineRegistry(authCatalog, { components, actions })builds the typedregistryResultinsidecreateMemo— real async action handlers dispatching toactor.send().<PlayUIProvider actor={actor} registryResult={registryResult()}><PlayRenderer /></PlayUIProvider>observesactor.currentViewand renders the active spec.- A
NavBarcomponent observesactorsignals directly for nav visibility. <DebugPanel actor={actor} />shows live state, auth status, and current route.- Browser tests in
test/browser/validate startup and interaction behavior.
// src/main.tsx (shape)render(() => <App />, document.getElementById("app")!);// src/App.tsx (shape)const createPlayer = definePlayer({ machine: authMachine });const actor = createPlayer() as AuthActor;actor.start();
export function App() { const registryResult = createMemo(() => 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 }), }); }, }, }), );
return ( <div class="demo-app" data-demo-shell> <header class="demo-header"> <h1 class="demo-title">XMachines Play Solid Demo</h1> <NavBar actor={actor} /> </header> <main class="demo-content" data-demo-content> <PlayUIProvider actor={actor} registryResult={registryResult()}> <PlayRenderer /> </PlayUIProvider> </main> <DebugPanel actor={actor} /> </div> );}Key Files
src/main.tsx- Vite bootstrap that mounts<App />via Solid’srendersrc/App.tsx- actor lifecycle, registry construction, andPlayUIProvider+PlayRenderercompositionsrc/components/- demo view components bound to catalog component keys (Home, Login, Dashboard, Profile, etc.)test/library-pattern.test.ts- architecture boundary and invariant assertionstest/browser/renderer-demo.browser.test.tsx- 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 — Solid never decides which view to render. - Passive Infrastructure:
PlayRendererobservesactor.currentViewsignals only. It holds no business state and makes no routing decisions. - Signal-Only Reactivity: Instead of React-like re-renders, Solid uses fine-grained
createEffectinternally within the renderer to react precisely when actor signals update.
Watcher Lifecycle and Cleanup Contract
This demo follows the canonical watcher lifecycle used across all @xmachines framework adapters:
notifyqueueMicrotaskgetPending()- Read actor signals and project Solid-local render state
- Re-arm with
watch()/watch(...signals)
Watcher notifications are one-shot. Cleanup is explicit and lifecycle-bound: Solid teardown uses onCleanup, and PlayRenderer handles internal watcher teardown natively on disposal.
Adapter Boundaries
PlayRenderer and defineRegistry stay passive infrastructure. Business validity remains actor-owned. The registry maps catalog component keys to Solid component implementations — the actor owns which key is active. The demo intentionally omits URL routing to isolate the renderer contract.
Available Scripts
These commands are defined in package.json:
| Command | Description |
|---|---|
npm run dev -w @xmachines/play-solid-demo | Start Vite dev server |
npm run build -w @xmachines/play-solid-demo | Build production bundle |
npm run preview -w @xmachines/play-solid-demo | Preview built bundle |
npm run test -w @xmachines/play-solid-demo | Run Vitest test suite |
npm run test:browser -w @xmachines/play-solid-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-solid-demonpm run test:browser -w @xmachines/play-solid-demoExpected result: library-pattern invariant tests pass and the browser renderer suite completes.
Learn More
Type Aliases
Variables
- About
- authCatalog
- Contact
- Dashboard
- DebugPanel
- Home
- Login
- NavBar
- NavBarView
- Navigation
- Overview
- Profile
- Settings
- Shell
- Stats