Skip to content

@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
  • PlayRenderer rendering actor-projected views with a typed defineRegistry catalog
  • Auth machine states drive view switching via auth.login / auth.logout events 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:

Terminal window
npm install
npm run dev -w @xmachines/play-solid-demo

Then open http://localhost:5173.

Step-by-Step Code Flow

Use this order to understand the implementation:

  1. src/main.tsx mounts <App /> to the DOM root via Solid’s render.
  2. src/App.tsx creates the actor at module scope via definePlayer({ machine: authMachine }) then calls the returned factory to produce the actor and starts it.
  3. defineRegistry(authCatalog, { components, actions }) builds the typed registryResult inside createMemo — real async action handlers dispatching to actor.send().
  4. <PlayUIProvider actor={actor} registryResult={registryResult()}><PlayRenderer /></PlayUIProvider> observes actor.currentView and renders the active spec.
  5. A NavBar component observes actor signals directly for nav visibility.
  6. <DebugPanel actor={actor} /> shows live state, auth status, and current route.
  7. 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’s render
  • src/App.tsx - actor lifecycle, registry construction, and PlayUIProvider + PlayRenderer composition
  • src/components/ - demo view components bound to catalog component keys (Home, Login, Dashboard, Profile, etc.)
  • test/library-pattern.test.ts - architecture boundary and invariant assertions
  • test/browser/renderer-demo.browser.test.tsx - browser-mode renderer coverage

State Machine & Architecture Details

The demo utilizes XMachines architectural invariants:

  1. Actor Authority: View components dispatch auth.login and auth.logout events to the actor. The actor evaluates guards and transitions — Solid never decides which view to render.
  2. Passive Infrastructure: PlayRenderer observes actor.currentView signals only. It holds no business state and makes no routing decisions.
  3. Signal-Only Reactivity: Instead of React-like re-renders, Solid uses fine-grained createEffect internally 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:

  1. notify
  2. queueMicrotask
  3. getPending()
  4. Read actor signals and project Solid-local render state
  5. 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:

CommandDescription
npm run dev -w @xmachines/play-solid-demoStart Vite dev server
npm run build -w @xmachines/play-solid-demoBuild production bundle
npm run preview -w @xmachines/play-solid-demoPreview built bundle
npm run test -w @xmachines/play-solid-demoRun Vitest test suite
npm run test:browser -w @xmachines/play-solid-demoRun browser-focused Vitest suite

Verification

Use these checks to validate README claims against the current demo implementation:

Terminal window
npm run test -w @xmachines/play-solid-demo
npm run test:browser -w @xmachines/play-solid-demo

Expected result: library-pattern invariant tests pass and the browser renderer suite completes.

Learn More

Type Aliases

Variables

Functions