@xmachines/play-tanstack-react-router 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.oxfmtrc.json +3 -0
- package/.oxlintrc.json +3 -0
- package/README.md +177 -0
- package/dist/extract-params.d.ts +45 -0
- package/dist/extract-params.d.ts.map +1 -0
- package/dist/extract-params.js +70 -0
- package/dist/extract-params.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/play-router-provider.d.ts +33 -0
- package/dist/play-router-provider.d.ts.map +1 -0
- package/dist/play-router-provider.js +31 -0
- package/dist/play-router-provider.js.map +1 -0
- package/dist/route-map.d.ts +101 -0
- package/dist/route-map.d.ts.map +1 -0
- package/dist/route-map.js +139 -0
- package/dist/route-map.js.map +1 -0
- package/dist/tanstack-router-bridge.d.ts +115 -0
- package/dist/tanstack-router-bridge.d.ts.map +1 -0
- package/dist/tanstack-router-bridge.js +112 -0
- package/dist/tanstack-router-bridge.js.map +1 -0
- package/dist/types.d.ts +26 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +9 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +10 -0
- package/dist/utils.js.map +1 -0
- package/examples/demo/README.md +100 -0
- package/examples/demo/docs/ARCHITECTURE.md +643 -0
- package/examples/demo/docs/INVARIANTS.md +461 -0
- package/examples/demo/docs/SWAP-REACT.md +635 -0
- package/examples/demo/index.html +16 -0
- package/examples/demo/package.json +39 -0
- package/examples/demo/src/App.tsx +148 -0
- package/examples/demo/src/components/About.tsx +49 -0
- package/examples/demo/src/components/Contact.tsx +43 -0
- package/examples/demo/src/components/Dashboard.tsx +46 -0
- package/examples/demo/src/components/DebugPanel.tsx +68 -0
- package/examples/demo/src/components/HeaderNav.tsx +103 -0
- package/examples/demo/src/components/Home.tsx +41 -0
- package/examples/demo/src/components/Login.tsx +82 -0
- package/examples/demo/src/components/Navigation.tsx +262 -0
- package/examples/demo/src/components/Profile.tsx +46 -0
- package/examples/demo/src/components/Register.tsx +109 -0
- package/examples/demo/src/components/Settings.tsx +92 -0
- package/examples/demo/src/components/index.ts +16 -0
- package/examples/demo/src/main.tsx +20 -0
- package/examples/demo/test/actor-authority.test.ts +50 -0
- package/examples/demo/test/browser/__screenshots__/back-button-duplicate.browser.test.tsx/Browser-back-button-navigates-through-unique-history--no-duplicates--1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/back-button-duplicate.browser.test.tsx/GAP-12--navigation-via-goto---events-creates-single-history-entries-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/back-button-duplicate.browser.test.tsx/GAP-12--navigation-via-goto---events-creates-single-history-entries-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--After-authentication-flow-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--Multiple-rapid-navigations-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--Multiple-rapid-navigations-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--URL-stays-in-sync-with-actor-state-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--URL-stays-in-sync-with-actor-state-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--Works-correctly-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--Works-correctly-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/direct-navigation.browser.test.ts/Direct-navigation-to--about-loads-about-page-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/direct-navigation.browser.test.ts/Direct-navigation-to--contact-loads-contact-page-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/direct-navigation.browser.test.ts/Direct-navigation-to--home-loads-home-page-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/direct-navigation.browser.test.ts/Direct-navigation-to-protected-route-while-authenticated-loads-dashboard-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/direct-navigation.browser.test.ts/Direct-navigation-to-protected-route-while-unauthenticated-redirects-to-login-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/exact-user-scenario.browser.test.tsx/Debug--Print-history-after-each-navigation-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/exact-user-scenario.browser.test.tsx/Debug--Print-history-after-each-navigation-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/exact-user-scenario.browser.test.tsx/EXACT-USER-SCENARIO--home---about---home---contact---home--then-back-3x-should-land-on-about-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/exact-user-scenario.browser.test.tsx/EXACT-USER-SCENARIO--home---about---home---contact---home--then-back-3x-should-land-on-about-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/guard-rejection.browser.test.tsx/E2E--Actor-Authority---infrastructure-cannot-override-guards-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/guard-rejection.browser.test.tsx/E2E--Actor-Authority---infrastructure-cannot-override-guards-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/guard-rejection.browser.test.tsx/E2E--Guards-reject-invalid-navigation-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/guard-rejection.browser.test.tsx/E2E--Guards-reject-invalid-navigation-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-investigation.browser.test.tsx/baseHistory-back---navigation--avoiding-window-history--1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-investigation.browser.test.tsx/baseHistory-back---navigation--avoiding-window-history--2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-forward-with-guard-transitions---authenticated-user-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-forward-with-guard-transitions---authenticated-user-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-forward-with-guard-transitions---unauthenticated-user-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-forward-with-guard-transitions---unauthenticated-user-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-with-guard---authenticated-user-navigates-back-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-with-guard---authenticated-user-navigates-back-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-with-guard---unauthenticated-user-stays-on-public-routes-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-with-guard---unauthenticated-user-stays-on-public-routes-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Forward-button-after-back---unique-history-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Forward-button-after-back---unique-history-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Forward-button-after-back-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Forward-button-after-back-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Navigate-forward-then-back---unique-history-entries-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Navigate-forward-then-back---unique-history-entries-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Rapid-back-forward-navigation-doesn-t-cause-duplicate-entries-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Rapid-back-forward-navigation-doesn-t-cause-duplicate-entries-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Single-back-navigation---about-to-home-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Single-back-navigation---about-to-home-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Single-back-navigation---contact-to-about-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Single-back-navigation---contact-to-about-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--View-syncs-with-URL-after-back-forward-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--View-syncs-with-URL-after-back-forward-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--View-syncs-with-URL-after-back-navigation-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--View-syncs-with-URL-after-back-navigation-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/login-flow.browser.test.tsx/E2E--User-can-log-in-and-see-dashboard-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/login-flow.browser.test.tsx/E2E--User-can-log-in-and-see-dashboard-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/navigation.browser.test.tsx/E2E--Navigation-reflects-actor-state-transitions-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/navigation.browser.test.tsx/E2E--Navigation-reflects-actor-state-transitions-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/Browser-back-forward-through-multiple-protected-routes-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/Browser-back-forward-through-multiple-protected-routes-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/Browser-back-navigates-from-dashboard-to-settings--protected-route--1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/Browser-back-navigates-from-dashboard-to-settings--protected-route--2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/RED--Browser-back-forward-through-multiple-protected-routes-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/RED--Browser-back-from-dashboard-to-settings--protected-route--1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/RED--Browser-back-navigates-from-dashboard-to-settings--protected-route--1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/settings-parameter.browser.test.tsx/Settings-Parameter-Display-should-display--account--section-when-navigating-to--settings-account-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/settings-parameter.browser.test.tsx/Settings-Parameter-Display-should-display--account--section-when-navigating-to--settings-account-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/settings-parameter.browser.test.tsx/Settings-Parameter-Display-should-display--general--section-when-navigating-to--settings--no-parameter--1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/settings-parameter.browser.test.tsx/Settings-Parameter-Display-should-display--general--section-when-navigating-to--settings--no-parameter--2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/settings-parameter.browser.test.tsx/Settings-Parameter-Display-should-display--profile--section-when-navigating-to--settings-profile-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/settings-parameter.browser.test.tsx/Settings-Parameter-Display-should-display--profile--section-when-navigating-to--settings-profile-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/settings-parameter.browser.test.tsx/Settings-Parameter-Display-should-update-section-display-when-clicking-section-navigation-buttons-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/settings-parameter.browser.test.tsx/Settings-Parameter-Display-should-update-section-display-when-clicking-section-navigation-buttons-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/settings-query-freeze.browser.test.ts/Settings-with-query-parameters-works-correctly-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/settings-query-freeze.browser.test.ts/Settings-with-section-parameter-works-correctly-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/state-driven.browser.test.ts/DEMO-04--State-Driven-Reset---Browser-back-sends-event-to-actor-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/state-driven.browser.test.ts/DEMO-04--State-Driven-Reset---Browser-back-sends-event-to-actor-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/state-driven.browser.test.ts/DEMO-04b--Browser-navigation-with-SignalSyncedHistory-integration-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/state-driven.browser.test.ts/DEMO-04b--Browser-navigation-with-SignalSyncedHistory-integration-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/tanstack-integration.browser.test.tsx/TanStack-Router-Integration-renders-with-RouterProvider-context-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/tanstack-integration.browser.test.tsx/TanStack-Router-Integration-renders-with-RouterProvider-context-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/test-multiple-back.browser.test.tsx/Multiple-back--Navigate-forward-3x-then-back-3x-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-1--Opening-with--someinvalidstate-stays-at-current-state-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-2--Opening-with--about-renders-About-component--not-Login-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-2b--Opening-with--home-renders-Home-component--not-Login-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-2c--Opening-with--contact-renders-Contact-component--not-Login-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-3--Back-forward-navigation---rendering-syncs-with-URL-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-3--Back-forward-navigation---rendering-syncs-with-URL-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-4--Auth-state-preserved-when-navigating-between-authenticated-anonymous-states-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-4--Auth-state-preserved-when-navigating-between-authenticated-anonymous-states-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-4b--Browser-back-forward-preserves-auth-state-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-4b--Browser-back-forward-preserves-auth-state-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-5--Protected-route-with-play-route-respects-authentication-guard-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-5--Protected-route-with-play-route-respects-authentication-guard-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/user-reported-scenario.browser.test.tsx/User-scenario--home---about---home---contact---home--then-back-3x-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/user-reported-scenario.browser.test.tsx/User-scenario--login---home---about---home---contact---home--then-back-3x-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Browser-back-button-sends-play-route-event-with-correct-state-ID-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Browser-back-button-sends-play-route-event-with-correct-state-ID-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Browser-back-button-sends-xstate-route-event-with-correct-state-ID-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Direct-URL-navigation-sends-play-route-event-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Direct-URL-navigation-sends-xstate-route-event-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Forward-button-sends-play-route-event-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Forward-button-sends-play-route-event-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Forward-button-sends-xstate-route-event-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/GAP-12-fix-preserved--No-duplicate-history-entries-with-play-route-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/GAP-12-fix-preserved--No-duplicate-history-entries-with-play-route-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Protected-route-sends-xstate-route-with-authentication-guard-1.png +0 -0
- package/examples/demo/test/browser/back-button-duplicate.browser.test.tsx +148 -0
- package/examples/demo/test/browser/back-forward-sync.browser.test.tsx +149 -0
- package/examples/demo/test/browser/direct-navigation.browser.test.ts +146 -0
- package/examples/demo/test/browser/exact-user-scenario.browser.test.tsx +207 -0
- package/examples/demo/test/browser/guard-rejection.browser.test.tsx +52 -0
- package/examples/demo/test/browser/history-investigation.browser.test.tsx +82 -0
- package/examples/demo/test/browser/history-navigation.browser.test.ts +351 -0
- package/examples/demo/test/browser/login-flow.browser.test.tsx +34 -0
- package/examples/demo/test/browser/navigation.browser.test.tsx +34 -0
- package/examples/demo/test/browser/protected-route-navigation.browser.test.tsx +161 -0
- package/examples/demo/test/browser/redirect-url-update.browser.test.tsx +140 -0
- package/examples/demo/test/browser/settings-parameter.browser.test.tsx +164 -0
- package/examples/demo/test/browser/settings-query-freeze.browser.test.ts +141 -0
- package/examples/demo/test/browser/state-driven.browser.test.ts +112 -0
- package/examples/demo/test/browser/tanstack-integration.browser.test.tsx +61 -0
- package/examples/demo/test/browser/uat-xstate-route-regression.browser.test.ts +58 -0
- package/examples/demo/test/browser/xstate-route-events.browser.test.ts +293 -0
- package/examples/demo/test/browser-back-view-rendering.test.ts +104 -0
- package/examples/demo/test/browser-e2e/auth-flow.browser.test.tsx +49 -0
- package/examples/demo/test/invalid-route-redirect.test.ts +40 -0
- package/examples/demo/test/passive-infra.test.ts +35 -0
- package/examples/demo/test/route-parameters.test.ts +539 -0
- package/examples/demo/test/signal-only.test.ts +54 -0
- package/examples/demo/test/strict-separation.test.ts +37 -0
- package/examples/demo/test/test-utils.ts +49 -0
- package/examples/demo/tsconfig.json +21 -0
- package/examples/demo/tsconfig.tsbuildinfo +1 -0
- package/examples/demo/vite.config.ts +13 -0
- package/examples/demo/vitest.browser.config.ts +72 -0
- package/examples/demo/vitest.config.e2e.browser.ts +28 -0
- package/examples/demo/vitest.config.ts +35 -0
- package/package.json +51 -0
- package/src/extract-params.ts +75 -0
- package/src/index.ts +31 -0
- package/src/play-router-provider.tsx +46 -0
- package/src/route-map.ts +158 -0
- package/src/tanstack-router-bridge.ts +135 -0
- package/src/types.ts +26 -0
- package/src/utils.ts +12 -0
- package/test/browser/__screenshots__/signal-synced-history.browser.test.ts/Browser-back-button-sends-route-navigate-event-to-actor-1.png +0 -0
- package/test/browser/__screenshots__/signal-synced-history.browser.test.ts/SignalSyncedHistory-prevents-circular-updates-1.png +0 -0
- package/test/browser/__screenshots__/signal-synced-history.browser.test.ts/SignalSyncedHistory-syncs-actor-route-to-browser-URL-1.png +0 -0
- package/test/browser/signal-synced-history.browser.test.ts +95 -0
- package/test/route-map.test.ts +107 -0
- package/test/tanstack-router-bridge.test.ts +318 -0
- package/test/urlpattern-integration.test.ts +145 -0
- package/tsconfig.json +16 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vitest.config.ts +35 -0
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
# Swapping React for Vue or Svelte
|
|
2
|
+
|
|
3
|
+
**Proof of DEMO-06:** This guide demonstrates that swapping React for Vue or Svelte requires **zero changes to business logic** - only the view renderer and component implementations change.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
1. [What Needs to Change](#what-needs-to-change)
|
|
10
|
+
2. [What Stays the Same](#what-stays-the-same)
|
|
11
|
+
3. [Vue Example](#vue-example)
|
|
12
|
+
4. [Svelte Example](#svelte-example)
|
|
13
|
+
5. [Why This Works](#why-this-works)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## What Needs to Change
|
|
18
|
+
|
|
19
|
+
| Layer | React | Vue | Svelte |
|
|
20
|
+
| ----------------------------- | ----------------------- | ------------------------------ | --------------------------------- |
|
|
21
|
+
| **Business Logic** | ✅ No changes | ✅ No changes | ✅ No changes |
|
|
22
|
+
| **State Machine** | ✅ No changes | ✅ No changes | ✅ No changes |
|
|
23
|
+
| **Component Catalog** | ✅ No changes | ✅ No changes | ✅ No changes |
|
|
24
|
+
| **Router Integration** | ✅ No changes | ✅ No changes | ✅ No changes |
|
|
25
|
+
| **View Renderer** | `@xmachines/play-react` | `@xmachines/play-vue` (future) | `@xmachines/play-svelte` (future) |
|
|
26
|
+
| **Component Implementations** | `.tsx` files | `.vue` files | `.svelte` files |
|
|
27
|
+
|
|
28
|
+
**Key Insight:** Only the view layer changes. Business logic, state machine, catalog, and router integration are framework-agnostic.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## What Stays the Same
|
|
33
|
+
|
|
34
|
+
### 1. State Machine (100% Unchanged)
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
// src/machines/auth-machine.ts - IDENTICAL for React, Vue, Svelte
|
|
38
|
+
import { createMachine, assign } from "xstate";
|
|
39
|
+
|
|
40
|
+
export const authMachine = createMachine({
|
|
41
|
+
id: "auth",
|
|
42
|
+
initial: "loggedOut",
|
|
43
|
+
context: {
|
|
44
|
+
isAuthenticated: false,
|
|
45
|
+
username: null,
|
|
46
|
+
routeParams: {},
|
|
47
|
+
queryParams: {},
|
|
48
|
+
},
|
|
49
|
+
states: {
|
|
50
|
+
loggedOut: {
|
|
51
|
+
meta: {
|
|
52
|
+
route: "/",
|
|
53
|
+
view: {
|
|
54
|
+
component: "LoginForm", // String reference, not framework-specific
|
|
55
|
+
props: { title: "Please Log In" },
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
on: {
|
|
59
|
+
"auth.login": {
|
|
60
|
+
target: "loggedIn",
|
|
61
|
+
actions: assign({
|
|
62
|
+
isAuthenticated: true,
|
|
63
|
+
username: ({ event }) => event.username,
|
|
64
|
+
}),
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
loggedIn: {
|
|
69
|
+
meta: {
|
|
70
|
+
route: "/dashboard",
|
|
71
|
+
view: {
|
|
72
|
+
component: "Dashboard",
|
|
73
|
+
props: { welcome: true },
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
// Always-guard validates state entry
|
|
77
|
+
always: [
|
|
78
|
+
{
|
|
79
|
+
target: "loggedOut",
|
|
80
|
+
guard: ({ context }) => !context.isAuthenticated,
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
on: {
|
|
84
|
+
"auth.logout": {
|
|
85
|
+
target: "loggedOut",
|
|
86
|
+
actions: assign({
|
|
87
|
+
isAuthenticated: false,
|
|
88
|
+
username: null,
|
|
89
|
+
}),
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Why It's Unchanged:**
|
|
98
|
+
|
|
99
|
+
- No React imports - machine is pure XState
|
|
100
|
+
- View structure in `meta.view` is plain data
|
|
101
|
+
- Component names are strings, not JSX imports
|
|
102
|
+
- Guards, actions, context are framework-agnostic
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
### 2. Component Catalog (100% Unchanged)
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// src/machines/catalog.ts - IDENTICAL for React, Vue, Svelte
|
|
110
|
+
import { z } from "zod";
|
|
111
|
+
import { defineCatalog } from "@xmachines/play-catalog";
|
|
112
|
+
|
|
113
|
+
export const catalog = defineCatalog({
|
|
114
|
+
LoginForm: z.object({
|
|
115
|
+
title: z.string(),
|
|
116
|
+
}),
|
|
117
|
+
Dashboard: z.object({
|
|
118
|
+
welcome: z.boolean(),
|
|
119
|
+
}),
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Why It's Unchanged:**
|
|
124
|
+
|
|
125
|
+
- No React imports - catalog uses Zod schemas
|
|
126
|
+
- Props are plain data structures
|
|
127
|
+
- Zod validation is framework-agnostic
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
### 3. Router Integration (100% Unchanged)
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
// Integration code - IDENTICAL for React, Vue, Svelte
|
|
135
|
+
import { definePlayer } from "@xmachines/play-xstate";
|
|
136
|
+
import { createPlayRouter } from "@xmachines/play-tanstack-react-router";
|
|
137
|
+
|
|
138
|
+
const createPlayer = definePlayer({
|
|
139
|
+
machine: authMachine,
|
|
140
|
+
catalog,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const actor = createPlayer();
|
|
144
|
+
actor.start();
|
|
145
|
+
|
|
146
|
+
const router = createPlayRouter({ actor });
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Why It's Unchanged:**
|
|
150
|
+
|
|
151
|
+
- `definePlayer` is framework-agnostic
|
|
152
|
+
- `createPlayRouter` observes actor signals, doesn't care about view framework
|
|
153
|
+
- Router sync happens via signals, not React-specific hooks
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Vue Example
|
|
158
|
+
|
|
159
|
+
### Installing Vue Renderer (Future)
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
npm install @xmachines/play-vue vue
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Vue LoginForm Component
|
|
166
|
+
|
|
167
|
+
```vue
|
|
168
|
+
<!-- src/components/LoginForm.vue -->
|
|
169
|
+
<script setup lang="ts">
|
|
170
|
+
import { ref } from "vue";
|
|
171
|
+
|
|
172
|
+
// Props from catalog
|
|
173
|
+
interface Props {
|
|
174
|
+
send: (event: any) => void;
|
|
175
|
+
title: string;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const props = defineProps<Props>();
|
|
179
|
+
|
|
180
|
+
// Local UI state ONLY (not business logic)
|
|
181
|
+
const username = ref("");
|
|
182
|
+
const password = ref("");
|
|
183
|
+
|
|
184
|
+
const handleSubmit = () => {
|
|
185
|
+
// Passive Infrastructure: Forward event to actor
|
|
186
|
+
props.send({
|
|
187
|
+
type: "auth.login",
|
|
188
|
+
username: username.value,
|
|
189
|
+
password: password.value,
|
|
190
|
+
});
|
|
191
|
+
};
|
|
192
|
+
</script>
|
|
193
|
+
|
|
194
|
+
<template>
|
|
195
|
+
<div style="max-width: 400px; margin: 50px auto; padding: 20px">
|
|
196
|
+
<h1>{{ title }}</h1>
|
|
197
|
+
<form @submit.prevent="handleSubmit">
|
|
198
|
+
<div style="margin-bottom: 15px">
|
|
199
|
+
<label for="username" style="display: block; margin-bottom: 5px"> Username </label>
|
|
200
|
+
<input
|
|
201
|
+
type="text"
|
|
202
|
+
id="username"
|
|
203
|
+
v-model="username"
|
|
204
|
+
style="width: 100%; padding: 8px; font-size: 16px"
|
|
205
|
+
required
|
|
206
|
+
/>
|
|
207
|
+
</div>
|
|
208
|
+
<div style="margin-bottom: 15px">
|
|
209
|
+
<label for="password" style="display: block; margin-bottom: 5px"> Password </label>
|
|
210
|
+
<input
|
|
211
|
+
type="password"
|
|
212
|
+
id="password"
|
|
213
|
+
v-model="password"
|
|
214
|
+
style="width: 100%; padding: 8px; font-size: 16px"
|
|
215
|
+
required
|
|
216
|
+
/>
|
|
217
|
+
</div>
|
|
218
|
+
<button
|
|
219
|
+
type="submit"
|
|
220
|
+
style="
|
|
221
|
+
width: 100%;
|
|
222
|
+
padding: 10px;
|
|
223
|
+
font-size: 16px;
|
|
224
|
+
background-color: #007bff;
|
|
225
|
+
color: white;
|
|
226
|
+
border: none;
|
|
227
|
+
border-radius: 4px;
|
|
228
|
+
cursor: pointer;
|
|
229
|
+
"
|
|
230
|
+
>
|
|
231
|
+
Log In
|
|
232
|
+
</button>
|
|
233
|
+
</form>
|
|
234
|
+
<p style="margin-top: 15px; font-size: 14px; color: #666">
|
|
235
|
+
Demo credentials: any username/password
|
|
236
|
+
</p>
|
|
237
|
+
</div>
|
|
238
|
+
</template>
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Key Similarities to React:**
|
|
242
|
+
|
|
243
|
+
- Component receives `send` function via props (same interface)
|
|
244
|
+
- Component forwards `auth.login` event (same event structure)
|
|
245
|
+
- Local form state managed by Vue (`ref` instead of `useState`)
|
|
246
|
+
- Business logic remains in actor - component just forwards events
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
### Vue Dashboard Component
|
|
251
|
+
|
|
252
|
+
```vue
|
|
253
|
+
<!-- src/components/Dashboard.vue -->
|
|
254
|
+
<script setup lang="ts">
|
|
255
|
+
interface Props {
|
|
256
|
+
send: (event: any) => void;
|
|
257
|
+
welcome: boolean;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const props = defineProps<Props>();
|
|
261
|
+
|
|
262
|
+
const handleLogout = () => {
|
|
263
|
+
// Passive Infrastructure: Forward event to actor
|
|
264
|
+
props.send({ type: "auth.logout" });
|
|
265
|
+
};
|
|
266
|
+
</script>
|
|
267
|
+
|
|
268
|
+
<template>
|
|
269
|
+
<div style="max-width: 800px; margin: 50px auto; padding: 20px">
|
|
270
|
+
<h1 v-if="welcome" style="margin-bottom: 20px">Welcome to the Dashboard!</h1>
|
|
271
|
+
<div style="margin-bottom: 20px">
|
|
272
|
+
<p style="font-size: 16px; line-height: 1.6">
|
|
273
|
+
This is a protected route - you can only see this because you're logged in.
|
|
274
|
+
</p>
|
|
275
|
+
<p style="font-size: 16px; line-height: 1.6; margin-top: 10px">
|
|
276
|
+
The authentication machine's guard protects this route from logged-out users,
|
|
277
|
+
demonstrating the <strong>Actor Authority</strong> invariant.
|
|
278
|
+
</p>
|
|
279
|
+
</div>
|
|
280
|
+
<button
|
|
281
|
+
@click="handleLogout"
|
|
282
|
+
style="
|
|
283
|
+
padding: 10px 20px;
|
|
284
|
+
font-size: 16px;
|
|
285
|
+
background-color: #dc3545;
|
|
286
|
+
color: white;
|
|
287
|
+
border: none;
|
|
288
|
+
border-radius: 4px;
|
|
289
|
+
cursor: pointer;
|
|
290
|
+
"
|
|
291
|
+
>
|
|
292
|
+
Logout
|
|
293
|
+
</button>
|
|
294
|
+
</div>
|
|
295
|
+
</template>
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
### Vue App Integration
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
// src/App.vue
|
|
304
|
+
<script setup lang="ts">
|
|
305
|
+
import { definePlayer } from "@xmachines/play-xstate";
|
|
306
|
+
import { createPlayRouter } from "@xmachines/play-tanstack-react-router";
|
|
307
|
+
import { PlayRenderer } from "@xmachines/play-vue"; // Vue renderer
|
|
308
|
+
import { defineComponents } from "@xmachines/play-catalog";
|
|
309
|
+
import { authMachine } from "./machines/auth-machine";
|
|
310
|
+
import { catalog } from "./machines/catalog";
|
|
311
|
+
import LoginForm from "./components/LoginForm.vue";
|
|
312
|
+
import Dashboard from "./components/Dashboard.vue";
|
|
313
|
+
|
|
314
|
+
// Define component implementations
|
|
315
|
+
const components = defineComponents(catalog, {
|
|
316
|
+
LoginForm,
|
|
317
|
+
Dashboard,
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// Create player factory
|
|
321
|
+
const createPlayer = definePlayer({
|
|
322
|
+
machine: authMachine,
|
|
323
|
+
catalog,
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// Create actor instance
|
|
327
|
+
const actor = createPlayer();
|
|
328
|
+
actor.start();
|
|
329
|
+
|
|
330
|
+
// Create router
|
|
331
|
+
const router = createPlayRouter({ actor });
|
|
332
|
+
</script>
|
|
333
|
+
|
|
334
|
+
<template>
|
|
335
|
+
<!-- PlayRenderer uses Vue's reactivity system to observe actor signals -->
|
|
336
|
+
<PlayRenderer :actor="actor" :components="components" />
|
|
337
|
+
</template>
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
**Key Changes from React:**
|
|
341
|
+
|
|
342
|
+
- Import `@xmachines/play-vue` instead of `@xmachines/play-react`
|
|
343
|
+
- Component files are `.vue` instead of `.tsx`
|
|
344
|
+
- Vue template syntax (`v-model`, `@click`) instead of JSX
|
|
345
|
+
|
|
346
|
+
**What Stayed the Same:**
|
|
347
|
+
|
|
348
|
+
- `definePlayer({ machine, catalog })` - identical
|
|
349
|
+
- `createPlayRouter({ actor })` - identical
|
|
350
|
+
- Business logic in `authMachine` - unchanged
|
|
351
|
+
- Component prop interface (`send`, `title`, `welcome`) - identical
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
## Svelte Example
|
|
356
|
+
|
|
357
|
+
### Installing Svelte Renderer (Future)
|
|
358
|
+
|
|
359
|
+
```bash
|
|
360
|
+
npm install @xmachines/play-svelte svelte
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Svelte LoginForm Component
|
|
364
|
+
|
|
365
|
+
```svelte
|
|
366
|
+
<!-- src/components/LoginForm.svelte -->
|
|
367
|
+
<script lang="ts">
|
|
368
|
+
// Props from catalog
|
|
369
|
+
export let send: (event: any) => void;
|
|
370
|
+
export let title: string;
|
|
371
|
+
|
|
372
|
+
// Local UI state ONLY (not business logic)
|
|
373
|
+
let username = "";
|
|
374
|
+
let password = "";
|
|
375
|
+
|
|
376
|
+
const handleSubmit = (e: Event) => {
|
|
377
|
+
e.preventDefault();
|
|
378
|
+
|
|
379
|
+
// Passive Infrastructure: Forward event to actor
|
|
380
|
+
send({
|
|
381
|
+
type: "auth.login",
|
|
382
|
+
username,
|
|
383
|
+
password,
|
|
384
|
+
});
|
|
385
|
+
};
|
|
386
|
+
</script>
|
|
387
|
+
|
|
388
|
+
<div style="max-width: 400px; margin: 50px auto; padding: 20px">
|
|
389
|
+
<h1>{title}</h1>
|
|
390
|
+
<form on:submit={handleSubmit}>
|
|
391
|
+
<div style="margin-bottom: 15px">
|
|
392
|
+
<label for="username" style="display: block; margin-bottom: 5px">
|
|
393
|
+
Username
|
|
394
|
+
</label>
|
|
395
|
+
<input
|
|
396
|
+
type="text"
|
|
397
|
+
id="username"
|
|
398
|
+
bind:value={username}
|
|
399
|
+
style="width: 100%; padding: 8px; font-size: 16px"
|
|
400
|
+
required
|
|
401
|
+
/>
|
|
402
|
+
</div>
|
|
403
|
+
<div style="margin-bottom: 15px">
|
|
404
|
+
<label for="password" style="display: block; margin-bottom: 5px">
|
|
405
|
+
Password
|
|
406
|
+
</label>
|
|
407
|
+
<input
|
|
408
|
+
type="password"
|
|
409
|
+
id="password"
|
|
410
|
+
bind:value={password}
|
|
411
|
+
style="width: 100%; padding: 8px; font-size: 16px"
|
|
412
|
+
required
|
|
413
|
+
/>
|
|
414
|
+
</div>
|
|
415
|
+
<button
|
|
416
|
+
type="submit"
|
|
417
|
+
style="width: 100%; padding: 10px; font-size: 16px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer;"
|
|
418
|
+
>
|
|
419
|
+
Log In
|
|
420
|
+
</button>
|
|
421
|
+
</form>
|
|
422
|
+
<p style="margin-top: 15px; font-size: 14px; color: #666">
|
|
423
|
+
Demo credentials: any username/password
|
|
424
|
+
</p>
|
|
425
|
+
</div>
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
**Key Similarities to React/Vue:**
|
|
429
|
+
|
|
430
|
+
- Component receives `send` function via props (same interface)
|
|
431
|
+
- Component forwards `auth.login` event (same event structure)
|
|
432
|
+
- Local form state managed by Svelte (`let` instead of `useState`/`ref`)
|
|
433
|
+
- Business logic remains in actor - component just forwards events
|
|
434
|
+
|
|
435
|
+
---
|
|
436
|
+
|
|
437
|
+
### Svelte Dashboard Component
|
|
438
|
+
|
|
439
|
+
```svelte
|
|
440
|
+
<!-- src/components/Dashboard.svelte -->
|
|
441
|
+
<script lang="ts">
|
|
442
|
+
export let send: (event: any) => void;
|
|
443
|
+
export let welcome: boolean;
|
|
444
|
+
|
|
445
|
+
const handleLogout = () => {
|
|
446
|
+
// Passive Infrastructure: Forward event to actor
|
|
447
|
+
send({ type: "auth.logout" });
|
|
448
|
+
};
|
|
449
|
+
</script>
|
|
450
|
+
|
|
451
|
+
<div style="max-width: 800px; margin: 50px auto; padding: 20px">
|
|
452
|
+
{#if welcome}
|
|
453
|
+
<h1 style="margin-bottom: 20px">Welcome to the Dashboard!</h1>
|
|
454
|
+
{/if}
|
|
455
|
+
<div style="margin-bottom: 20px">
|
|
456
|
+
<p style="font-size: 16px; line-height: 1.6">
|
|
457
|
+
This is a protected route - you can only see this because you're logged
|
|
458
|
+
in.
|
|
459
|
+
</p>
|
|
460
|
+
<p style="font-size: 16px; line-height: 1.6; margin-top: 10px">
|
|
461
|
+
The authentication machine's guard protects this route from logged-out
|
|
462
|
+
users, demonstrating the <strong>Actor Authority</strong> invariant.
|
|
463
|
+
</p>
|
|
464
|
+
</div>
|
|
465
|
+
<button
|
|
466
|
+
on:click={handleLogout}
|
|
467
|
+
style="padding: 10px 20px; font-size: 16px; background-color: #dc3545; color: white; border: none; border-radius: 4px; cursor: pointer;"
|
|
468
|
+
>
|
|
469
|
+
Logout
|
|
470
|
+
</button>
|
|
471
|
+
</div>
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
---
|
|
475
|
+
|
|
476
|
+
### Svelte App Integration
|
|
477
|
+
|
|
478
|
+
```svelte
|
|
479
|
+
<!-- src/App.svelte -->
|
|
480
|
+
<script lang="ts">
|
|
481
|
+
import { definePlayer } from "@xmachines/play-xstate";
|
|
482
|
+
import { createPlayRouter } from "@xmachines/play-tanstack-react-router";
|
|
483
|
+
import { PlayRenderer } from "@xmachines/play-svelte"; // Svelte renderer
|
|
484
|
+
import { defineComponents } from "@xmachines/play-catalog";
|
|
485
|
+
import { authMachine } from "./machines/auth-machine";
|
|
486
|
+
import { catalog } from "./machines/catalog";
|
|
487
|
+
import LoginForm from "./components/LoginForm.svelte";
|
|
488
|
+
import Dashboard from "./components/Dashboard.svelte";
|
|
489
|
+
|
|
490
|
+
// Define component implementations
|
|
491
|
+
const components = defineComponents(catalog, {
|
|
492
|
+
LoginForm,
|
|
493
|
+
Dashboard,
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
// Create player factory
|
|
497
|
+
const createPlayer = definePlayer({
|
|
498
|
+
machine: authMachine,
|
|
499
|
+
catalog,
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
// Create actor instance
|
|
503
|
+
const actor = createPlayer();
|
|
504
|
+
actor.start();
|
|
505
|
+
|
|
506
|
+
// Create router
|
|
507
|
+
const router = createPlayRouter({ actor });
|
|
508
|
+
</script>
|
|
509
|
+
|
|
510
|
+
<!-- PlayRenderer uses Svelte stores to observe actor signals -->
|
|
511
|
+
<PlayRenderer {actor} {components} />
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
**Key Changes from React/Vue:**
|
|
515
|
+
|
|
516
|
+
- Import `@xmachines/play-svelte` instead of `@xmachines/play-react`
|
|
517
|
+
- Component files are `.svelte` instead of `.tsx`/`.vue`
|
|
518
|
+
- Svelte template syntax (`bind:value`, `on:click`) instead of JSX/Vue
|
|
519
|
+
|
|
520
|
+
**What Stayed the Same:**
|
|
521
|
+
|
|
522
|
+
- `definePlayer({ machine, catalog })` - identical
|
|
523
|
+
- `createPlayRouter({ actor })` - identical
|
|
524
|
+
- Business logic in `authMachine` - unchanged
|
|
525
|
+
- Component prop interface (`send`, `title`, `welcome`) - identical
|
|
526
|
+
|
|
527
|
+
---
|
|
528
|
+
|
|
529
|
+
## Why This Works
|
|
530
|
+
|
|
531
|
+
### 1. Strict Separation Invariant
|
|
532
|
+
|
|
533
|
+
**Business logic has zero framework imports.**
|
|
534
|
+
|
|
535
|
+
`auth-machine.ts` and `catalog.ts` have no React, Vue, or Svelte imports. They use XState and Zod - both framework-agnostic libraries.
|
|
536
|
+
|
|
537
|
+
**Verification:**
|
|
538
|
+
|
|
539
|
+
```bash
|
|
540
|
+
grep -rn "from ['\"']react\|from ['\"']vue\|from ['\"']svelte" src/machines/
|
|
541
|
+
# Expected: no output (zero framework imports)
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
---
|
|
545
|
+
|
|
546
|
+
### 2. Signal-Only Reactivity Invariant
|
|
547
|
+
|
|
548
|
+
**Business state lives in actor signals, not framework-specific state.**
|
|
549
|
+
|
|
550
|
+
- React: `useSignalEffect` observes `actor.state` signal
|
|
551
|
+
- Vue: Vue's reactivity system observes `actor.state` signal via `watchEffect`
|
|
552
|
+
- Svelte: Svelte stores observe `actor.state` signal via `derived`
|
|
553
|
+
|
|
554
|
+
All three frameworks observe the **same signal** - the actor's state signal. Framework-specific reactivity is only for rendering, not business logic.
|
|
555
|
+
|
|
556
|
+
---
|
|
557
|
+
|
|
558
|
+
### 3. Passive Infrastructure Invariant
|
|
559
|
+
|
|
560
|
+
**Components forward events via `send()`, don't control navigation.**
|
|
561
|
+
|
|
562
|
+
All three implementations:
|
|
563
|
+
|
|
564
|
+
```typescript
|
|
565
|
+
// React
|
|
566
|
+
send({ type: "auth.login", username, password });
|
|
567
|
+
|
|
568
|
+
// Vue
|
|
569
|
+
props.send({ type: "auth.login", username: username.value, password: password.value });
|
|
570
|
+
|
|
571
|
+
// Svelte
|
|
572
|
+
send({ type: "auth.login", username, password });
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
Same event structure. Same `send` function interface. Actor processes event identically regardless of framework.
|
|
576
|
+
|
|
577
|
+
---
|
|
578
|
+
|
|
579
|
+
### 4. meta.view Pattern
|
|
580
|
+
|
|
581
|
+
**View structure defined in metadata, not framework-specific code.**
|
|
582
|
+
|
|
583
|
+
```typescript
|
|
584
|
+
meta: {
|
|
585
|
+
view: {
|
|
586
|
+
component: "LoginForm", // String reference
|
|
587
|
+
props: { title: "Please Log In" },
|
|
588
|
+
},
|
|
589
|
+
}
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
- React renderer resolves `"LoginForm"` → `LoginForm.tsx`
|
|
593
|
+
- Vue renderer resolves `"LoginForm"` → `LoginForm.vue`
|
|
594
|
+
- Svelte renderer resolves `"LoginForm"` → `LoginForm.svelte`
|
|
595
|
+
|
|
596
|
+
Same metadata structure. Framework-specific resolution happens at runtime by renderer.
|
|
597
|
+
|
|
598
|
+
---
|
|
599
|
+
|
|
600
|
+
## Summary
|
|
601
|
+
|
|
602
|
+
### What Changes
|
|
603
|
+
|
|
604
|
+
| Item | React | Vue | Svelte |
|
|
605
|
+
| ---------------- | ----------------------- | --------------------- | ------------------------ |
|
|
606
|
+
| Renderer package | `@xmachines/play-react` | `@xmachines/play-vue` | `@xmachines/play-svelte` |
|
|
607
|
+
| Component files | `.tsx` | `.vue` | `.svelte` |
|
|
608
|
+
| Template syntax | JSX | Vue templates | Svelte templates |
|
|
609
|
+
| Local state | `useState` | `ref` | `let` |
|
|
610
|
+
|
|
611
|
+
### What Stays the Same (100% Unchanged)
|
|
612
|
+
|
|
613
|
+
- ✅ State machine definition (`auth-machine.ts`)
|
|
614
|
+
- ✅ Component catalog (`catalog.ts`)
|
|
615
|
+
- ✅ Guards, actions, context
|
|
616
|
+
- ✅ Event types (`auth.login`, `auth.logout`, `play.route`)
|
|
617
|
+
- ✅ Component prop interface (`send`, `title`, `welcome`)
|
|
618
|
+
- ✅ Router integration (`createPlayRouter`)
|
|
619
|
+
- ✅ Business logic, validation, authentication flow
|
|
620
|
+
|
|
621
|
+
---
|
|
622
|
+
|
|
623
|
+
## Try It Yourself
|
|
624
|
+
|
|
625
|
+
1. **Clone this demo** and verify React works
|
|
626
|
+
2. **Implement Vue components** following patterns above
|
|
627
|
+
3. **Swap renderer** in `App.vue`
|
|
628
|
+
4. **Run `npm run dev`** - everything works!
|
|
629
|
+
5. **Verify tests still pass** - invariants proven
|
|
630
|
+
|
|
631
|
+
**Result:** Business logic unchanged. Framework swap complete.
|
|
632
|
+
|
|
633
|
+
---
|
|
634
|
+
|
|
635
|
+
**Questions?** Read [ARCHITECTURE.md](./ARCHITECTURE.md) for line-by-line code walkthrough or the RFC specification for invariant explanations.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>XMachines Play - React Demo</title>
|
|
7
|
+
<meta
|
|
8
|
+
name="description"
|
|
9
|
+
content="Universal Player Architecture demonstration with authentication flow"
|
|
10
|
+
/>
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<div id="root"></div>
|
|
14
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
15
|
+
</body>
|
|
16
|
+
</html>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xmachines/play-tanstack-react-router-demo",
|
|
3
|
+
"version": "1.0.0-beta.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Universal Player Architecture demonstration with authentication flow",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "vite",
|
|
9
|
+
"build": "vite build",
|
|
10
|
+
"preview": "vite preview",
|
|
11
|
+
"test": "vitest run",
|
|
12
|
+
"test:vitest": "vitest run",
|
|
13
|
+
"test:browser": "NODE_OPTIONS='--max-old-space-size=8192' vitest run --config vitest.config.e2e.browser.ts",
|
|
14
|
+
"test:browser:full": "NODE_OPTIONS='--max-old-space-size=8192' vitest run --config vitest.browser.config.ts",
|
|
15
|
+
"test:e2e": "npm run test:browser"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@tanstack/react-router": "^1.166.7",
|
|
19
|
+
"@xmachines/play": "1.0.0-beta.1",
|
|
20
|
+
"@xmachines/play-catalog": "1.0.0-beta.1",
|
|
21
|
+
"@xmachines/play-react": "1.0.0-beta.1",
|
|
22
|
+
"@xmachines/play-router": "1.0.0-beta.1",
|
|
23
|
+
"@xmachines/play-router-shared": "1.0.0-beta.1",
|
|
24
|
+
"@xmachines/play-tanstack-react-router": "1.0.0-beta.1",
|
|
25
|
+
"@xmachines/play-xstate": "1.0.0-beta.1",
|
|
26
|
+
"react": "^19.0.0",
|
|
27
|
+
"react-dom": "^19.0.0",
|
|
28
|
+
"xstate": "^5.28.0",
|
|
29
|
+
"zod": "^4.3.6"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/react": "^19.0.0",
|
|
33
|
+
"@types/react-dom": "^19.0.0",
|
|
34
|
+
"@vitejs/plugin-react": "^5.1.4",
|
|
35
|
+
"jsdom": "^28.1.0",
|
|
36
|
+
"typescript": "^5.7.0",
|
|
37
|
+
"vite": "^7.3.1"
|
|
38
|
+
}
|
|
39
|
+
}
|