Skip to content

@xmachines/play-vue-demo

Examples / @xmachines/play-vue-demo

Vue 3 renderer demo for @xmachines/play-vue — 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
  • Vue Composition API mapping to TC39 Signals lifecycle
  • 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-vue-demo

Then open http://localhost:5173.

Step-by-Step Code Flow

Use this order to understand the implementation:

  1. src/main.ts calls definePlayer({ machine: authMachine }), starts the actor, and mounts the Vue app.
  2. The actor is provided to all components via app.provide("actor", actor).
  3. src/App.vue injects the actor, declares a typed ComponentsMap<typeof authCatalog> for demoViews, and builds the registryResult with defineRegistry(authCatalog, { components: demoViews, actions }) inside computed — action handlers use satisfies ActionFn for catalog-typed safety and dispatch to actor.send().
  4. <PlayUIProvider :actor="typedActor" :registryResult="registryResult"><PlayRenderer /></PlayUIProvider> observes actor.currentView and renders the active spec.
  5. A NavBar SFC observes actor signals directly for nav visibility.
  6. <DebugPanel :actor="typedActor" /> shows live state, auth status, and current route.
  7. HMR cleanup calls actor.stop() via import.meta.hot.dispose.
  8. Browser tests in test/browser/ validate startup and interaction behavior.
src/main.ts
const createPlayer = definePlayer({ machine: authMachine });
const actor = createPlayer() as AuthActor;
actor.start();
const app = createApp(App);
app.provide("actor", actor);
app.mount("#app");
src/App.vue
<script setup lang="ts">
import type { ComponentsMap } from "@xmachines/play-vue";
import type { ActionFn } from "@json-render/vue";
const typedActor = assertNonNullable(inject<AuthActor>("actor"), "actor");
const demoViews: ComponentsMap<typeof authCatalog> = {
Home: HomeSFC,
About: AboutSFC,
Contact: ContactSFC,
Login: LoginSFC,
Dashboard: DashboardSFC,
Overview: OverviewSFC,
Stats: StatsSFC,
Profile: ProfileSFC,
Settings: SettingsSFC,
Navigation: NavigationSFC,
NavBar: NavBarViewSFC,
};
const registryResult = computed(() =>
defineRegistry(authCatalog, {
components: demoViews,
actions: {
login: (async (args) => {
typedActor.send({
type: "auth.login",
username: assertNonNullable(args, "args").username,
});
}) satisfies ActionFn<typeof authCatalog, "login">,
logout: async () => typedActor.send({ type: "auth.logout" }),
route: (async (args) => {
const { to, params } = assertNonNullable(args, "args");
typedActor.send({
type: "play.route",
to,
...(params != null && { params }),
});
}) satisfies ActionFn<typeof authCatalog, "route">,
},
}),
);
</script>
<template>
<div class="demo-app" data-demo-shell>
<header class="demo-header">
<h1 class="demo-title">XMachines Play Vue Demo</h1>
<NavBar :actor="typedActor" />
</header>
<main class="demo-content" data-demo-content>
<PlayUIProvider :actor="typedActor" :registryResult="registryResult">
<PlayRenderer />
</PlayUIProvider>
</main>
<DebugPanel :actor="typedActor" />
</div>
</template>

Key Files

  • src/main.ts - actor creation/start and Vue app mount with actor injection
  • src/App.vue - typed ComponentsMap declaration, registry construction, and PlayUIProvider + PlayRenderer composition
  • src/catalog.ts - authCatalog built via defineCatalog with the @json-render/vue schema
  • src/components/ - demo view components bound to catalog component keys (Home, Login, Dashboard, Profile, etc.)
  • src/index.ts - re-exports all components and catalog for library-pattern testing
  • test/library-pattern.test.ts - architecture boundary and invariant assertions
  • test/browser/renderer-demo.browser.test.ts - 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 — Vue 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: The bridge leverages Vue’s reactivity model internally to react precisely when actor signals update, without polluting the component tree with reactive refs holding business state.

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 sync Vue-local render state
  5. 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 unmount.

Adapter Boundaries

PlayRenderer and defineRegistry stay passive infrastructure. Business validity remains actor-owned. The registry maps catalog component keys to Vue SFC 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-vue-demoStart Vite dev server
npm run build -w @xmachines/play-vue-demoBuild production bundle
npm run preview -w @xmachines/play-vue-demoPreview built bundle
npm run test -w @xmachines/play-vue-demoRun Vitest test suite
npm run test:browser -w @xmachines/play-vue-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-vue-demo
npm run test:browser -w @xmachines/play-vue-demo

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

Learn More

Type Aliases

Variables