@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.
Files changed (203) hide show
  1. package/.oxfmtrc.json +3 -0
  2. package/.oxlintrc.json +3 -0
  3. package/README.md +177 -0
  4. package/dist/extract-params.d.ts +45 -0
  5. package/dist/extract-params.d.ts.map +1 -0
  6. package/dist/extract-params.js +70 -0
  7. package/dist/extract-params.js.map +1 -0
  8. package/dist/index.d.ts +18 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +17 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/play-router-provider.d.ts +33 -0
  13. package/dist/play-router-provider.d.ts.map +1 -0
  14. package/dist/play-router-provider.js +31 -0
  15. package/dist/play-router-provider.js.map +1 -0
  16. package/dist/route-map.d.ts +101 -0
  17. package/dist/route-map.d.ts.map +1 -0
  18. package/dist/route-map.js +139 -0
  19. package/dist/route-map.js.map +1 -0
  20. package/dist/tanstack-router-bridge.d.ts +115 -0
  21. package/dist/tanstack-router-bridge.d.ts.map +1 -0
  22. package/dist/tanstack-router-bridge.js +112 -0
  23. package/dist/tanstack-router-bridge.js.map +1 -0
  24. package/dist/types.d.ts +26 -0
  25. package/dist/types.d.ts.map +1 -0
  26. package/dist/types.js +7 -0
  27. package/dist/types.js.map +1 -0
  28. package/dist/utils.d.ts +9 -0
  29. package/dist/utils.d.ts.map +1 -0
  30. package/dist/utils.js +10 -0
  31. package/dist/utils.js.map +1 -0
  32. package/examples/demo/README.md +100 -0
  33. package/examples/demo/docs/ARCHITECTURE.md +643 -0
  34. package/examples/demo/docs/INVARIANTS.md +461 -0
  35. package/examples/demo/docs/SWAP-REACT.md +635 -0
  36. package/examples/demo/index.html +16 -0
  37. package/examples/demo/package.json +39 -0
  38. package/examples/demo/src/App.tsx +148 -0
  39. package/examples/demo/src/components/About.tsx +49 -0
  40. package/examples/demo/src/components/Contact.tsx +43 -0
  41. package/examples/demo/src/components/Dashboard.tsx +46 -0
  42. package/examples/demo/src/components/DebugPanel.tsx +68 -0
  43. package/examples/demo/src/components/HeaderNav.tsx +103 -0
  44. package/examples/demo/src/components/Home.tsx +41 -0
  45. package/examples/demo/src/components/Login.tsx +82 -0
  46. package/examples/demo/src/components/Navigation.tsx +262 -0
  47. package/examples/demo/src/components/Profile.tsx +46 -0
  48. package/examples/demo/src/components/Register.tsx +109 -0
  49. package/examples/demo/src/components/Settings.tsx +92 -0
  50. package/examples/demo/src/components/index.ts +16 -0
  51. package/examples/demo/src/main.tsx +20 -0
  52. package/examples/demo/test/actor-authority.test.ts +50 -0
  53. 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
  54. 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
  55. 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
  56. package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--After-authentication-flow-1.png +0 -0
  57. package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--Multiple-rapid-navigations-1.png +0 -0
  58. package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--Multiple-rapid-navigations-2.png +0 -0
  59. 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
  60. 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
  61. package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--Works-correctly-1.png +0 -0
  62. package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--Works-correctly-2.png +0 -0
  63. package/examples/demo/test/browser/__screenshots__/direct-navigation.browser.test.ts/Direct-navigation-to--about-loads-about-page-1.png +0 -0
  64. package/examples/demo/test/browser/__screenshots__/direct-navigation.browser.test.ts/Direct-navigation-to--contact-loads-contact-page-1.png +0 -0
  65. package/examples/demo/test/browser/__screenshots__/direct-navigation.browser.test.ts/Direct-navigation-to--home-loads-home-page-1.png +0 -0
  66. package/examples/demo/test/browser/__screenshots__/direct-navigation.browser.test.ts/Direct-navigation-to-protected-route-while-authenticated-loads-dashboard-1.png +0 -0
  67. 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
  68. package/examples/demo/test/browser/__screenshots__/exact-user-scenario.browser.test.tsx/Debug--Print-history-after-each-navigation-1.png +0 -0
  69. package/examples/demo/test/browser/__screenshots__/exact-user-scenario.browser.test.tsx/Debug--Print-history-after-each-navigation-2.png +0 -0
  70. 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
  71. 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
  72. package/examples/demo/test/browser/__screenshots__/guard-rejection.browser.test.tsx/E2E--Actor-Authority---infrastructure-cannot-override-guards-1.png +0 -0
  73. package/examples/demo/test/browser/__screenshots__/guard-rejection.browser.test.tsx/E2E--Actor-Authority---infrastructure-cannot-override-guards-2.png +0 -0
  74. package/examples/demo/test/browser/__screenshots__/guard-rejection.browser.test.tsx/E2E--Guards-reject-invalid-navigation-1.png +0 -0
  75. package/examples/demo/test/browser/__screenshots__/guard-rejection.browser.test.tsx/E2E--Guards-reject-invalid-navigation-2.png +0 -0
  76. package/examples/demo/test/browser/__screenshots__/history-investigation.browser.test.tsx/baseHistory-back---navigation--avoiding-window-history--1.png +0 -0
  77. package/examples/demo/test/browser/__screenshots__/history-investigation.browser.test.tsx/baseHistory-back---navigation--avoiding-window-history--2.png +0 -0
  78. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-forward-with-guard-transitions---authenticated-user-1.png +0 -0
  79. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-forward-with-guard-transitions---authenticated-user-2.png +0 -0
  80. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-forward-with-guard-transitions---unauthenticated-user-1.png +0 -0
  81. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-forward-with-guard-transitions---unauthenticated-user-2.png +0 -0
  82. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-with-guard---authenticated-user-navigates-back-1.png +0 -0
  83. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-with-guard---authenticated-user-navigates-back-2.png +0 -0
  84. 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
  85. 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
  86. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Forward-button-after-back---unique-history-1.png +0 -0
  87. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Forward-button-after-back---unique-history-2.png +0 -0
  88. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Forward-button-after-back-1.png +0 -0
  89. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Forward-button-after-back-2.png +0 -0
  90. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Navigate-forward-then-back---unique-history-entries-1.png +0 -0
  91. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Navigate-forward-then-back---unique-history-entries-2.png +0 -0
  92. 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
  93. 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
  94. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Single-back-navigation---about-to-home-1.png +0 -0
  95. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Single-back-navigation---about-to-home-2.png +0 -0
  96. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Single-back-navigation---contact-to-about-1.png +0 -0
  97. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Single-back-navigation---contact-to-about-2.png +0 -0
  98. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--View-syncs-with-URL-after-back-forward-1.png +0 -0
  99. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--View-syncs-with-URL-after-back-forward-2.png +0 -0
  100. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--View-syncs-with-URL-after-back-navigation-1.png +0 -0
  101. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--View-syncs-with-URL-after-back-navigation-2.png +0 -0
  102. package/examples/demo/test/browser/__screenshots__/login-flow.browser.test.tsx/E2E--User-can-log-in-and-see-dashboard-1.png +0 -0
  103. package/examples/demo/test/browser/__screenshots__/login-flow.browser.test.tsx/E2E--User-can-log-in-and-see-dashboard-2.png +0 -0
  104. package/examples/demo/test/browser/__screenshots__/navigation.browser.test.tsx/E2E--Navigation-reflects-actor-state-transitions-1.png +0 -0
  105. package/examples/demo/test/browser/__screenshots__/navigation.browser.test.tsx/E2E--Navigation-reflects-actor-state-transitions-2.png +0 -0
  106. package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/Browser-back-forward-through-multiple-protected-routes-1.png +0 -0
  107. package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/Browser-back-forward-through-multiple-protected-routes-2.png +0 -0
  108. 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
  109. 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
  110. package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/RED--Browser-back-forward-through-multiple-protected-routes-1.png +0 -0
  111. 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
  112. 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
  113. 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
  114. 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
  115. 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
  116. 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
  117. 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
  118. 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
  119. 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
  120. 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
  121. package/examples/demo/test/browser/__screenshots__/settings-query-freeze.browser.test.ts/Settings-with-query-parameters-works-correctly-1.png +0 -0
  122. package/examples/demo/test/browser/__screenshots__/settings-query-freeze.browser.test.ts/Settings-with-section-parameter-works-correctly-1.png +0 -0
  123. 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
  124. 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
  125. package/examples/demo/test/browser/__screenshots__/state-driven.browser.test.ts/DEMO-04b--Browser-navigation-with-SignalSyncedHistory-integration-1.png +0 -0
  126. package/examples/demo/test/browser/__screenshots__/state-driven.browser.test.ts/DEMO-04b--Browser-navigation-with-SignalSyncedHistory-integration-2.png +0 -0
  127. package/examples/demo/test/browser/__screenshots__/tanstack-integration.browser.test.tsx/TanStack-Router-Integration-renders-with-RouterProvider-context-1.png +0 -0
  128. package/examples/demo/test/browser/__screenshots__/tanstack-integration.browser.test.tsx/TanStack-Router-Integration-renders-with-RouterProvider-context-2.png +0 -0
  129. package/examples/demo/test/browser/__screenshots__/test-multiple-back.browser.test.tsx/Multiple-back--Navigate-forward-3x-then-back-3x-1.png +0 -0
  130. 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
  131. 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
  132. 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
  133. 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
  134. 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
  135. 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
  136. 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
  137. 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
  138. 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
  139. 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
  140. 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
  141. 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
  142. 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
  143. 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
  144. 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
  145. 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
  146. 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
  147. package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Direct-URL-navigation-sends-play-route-event-1.png +0 -0
  148. package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Direct-URL-navigation-sends-xstate-route-event-1.png +0 -0
  149. package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Forward-button-sends-play-route-event-1.png +0 -0
  150. package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Forward-button-sends-play-route-event-2.png +0 -0
  151. package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Forward-button-sends-xstate-route-event-1.png +0 -0
  152. 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
  153. 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
  154. package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Protected-route-sends-xstate-route-with-authentication-guard-1.png +0 -0
  155. package/examples/demo/test/browser/back-button-duplicate.browser.test.tsx +148 -0
  156. package/examples/demo/test/browser/back-forward-sync.browser.test.tsx +149 -0
  157. package/examples/demo/test/browser/direct-navigation.browser.test.ts +146 -0
  158. package/examples/demo/test/browser/exact-user-scenario.browser.test.tsx +207 -0
  159. package/examples/demo/test/browser/guard-rejection.browser.test.tsx +52 -0
  160. package/examples/demo/test/browser/history-investigation.browser.test.tsx +82 -0
  161. package/examples/demo/test/browser/history-navigation.browser.test.ts +351 -0
  162. package/examples/demo/test/browser/login-flow.browser.test.tsx +34 -0
  163. package/examples/demo/test/browser/navigation.browser.test.tsx +34 -0
  164. package/examples/demo/test/browser/protected-route-navigation.browser.test.tsx +161 -0
  165. package/examples/demo/test/browser/redirect-url-update.browser.test.tsx +140 -0
  166. package/examples/demo/test/browser/settings-parameter.browser.test.tsx +164 -0
  167. package/examples/demo/test/browser/settings-query-freeze.browser.test.ts +141 -0
  168. package/examples/demo/test/browser/state-driven.browser.test.ts +112 -0
  169. package/examples/demo/test/browser/tanstack-integration.browser.test.tsx +61 -0
  170. package/examples/demo/test/browser/uat-xstate-route-regression.browser.test.ts +58 -0
  171. package/examples/demo/test/browser/xstate-route-events.browser.test.ts +293 -0
  172. package/examples/demo/test/browser-back-view-rendering.test.ts +104 -0
  173. package/examples/demo/test/browser-e2e/auth-flow.browser.test.tsx +49 -0
  174. package/examples/demo/test/invalid-route-redirect.test.ts +40 -0
  175. package/examples/demo/test/passive-infra.test.ts +35 -0
  176. package/examples/demo/test/route-parameters.test.ts +539 -0
  177. package/examples/demo/test/signal-only.test.ts +54 -0
  178. package/examples/demo/test/strict-separation.test.ts +37 -0
  179. package/examples/demo/test/test-utils.ts +49 -0
  180. package/examples/demo/tsconfig.json +21 -0
  181. package/examples/demo/tsconfig.tsbuildinfo +1 -0
  182. package/examples/demo/vite.config.ts +13 -0
  183. package/examples/demo/vitest.browser.config.ts +72 -0
  184. package/examples/demo/vitest.config.e2e.browser.ts +28 -0
  185. package/examples/demo/vitest.config.ts +35 -0
  186. package/package.json +51 -0
  187. package/src/extract-params.ts +75 -0
  188. package/src/index.ts +31 -0
  189. package/src/play-router-provider.tsx +46 -0
  190. package/src/route-map.ts +158 -0
  191. package/src/tanstack-router-bridge.ts +135 -0
  192. package/src/types.ts +26 -0
  193. package/src/utils.ts +12 -0
  194. package/test/browser/__screenshots__/signal-synced-history.browser.test.ts/Browser-back-button-sends-route-navigate-event-to-actor-1.png +0 -0
  195. package/test/browser/__screenshots__/signal-synced-history.browser.test.ts/SignalSyncedHistory-prevents-circular-updates-1.png +0 -0
  196. package/test/browser/__screenshots__/signal-synced-history.browser.test.ts/SignalSyncedHistory-syncs-actor-route-to-browser-URL-1.png +0 -0
  197. package/test/browser/signal-synced-history.browser.test.ts +95 -0
  198. package/test/route-map.test.ts +107 -0
  199. package/test/tanstack-router-bridge.test.ts +318 -0
  200. package/test/urlpattern-integration.test.ts +145 -0
  201. package/tsconfig.json +16 -0
  202. package/tsconfig.tsbuildinfo +1 -0
  203. package/vitest.config.ts +35 -0
@@ -0,0 +1,643 @@
1
+ # Demo Application Architecture
2
+
3
+ **Target reading time:** 15 minutes
4
+
5
+ This document provides a line-by-line walkthrough of the demo application's architecture, showing how the Universal Player Architecture implements all 5 architectural invariants.
6
+
7
+ ---
8
+
9
+ ## Table of Contents
10
+
11
+ 1. [Overview](#overview)
12
+ 2. [Data Flow](#data-flow)
13
+ 3. [File-by-File Explanation](#file-by-file-explanation)
14
+ 4. [Key Patterns](#key-patterns)
15
+ 5. [Verification](#verification)
16
+
17
+ ---
18
+
19
+ ## Overview
20
+
21
+ ### What This Demo Proves
22
+
23
+ This demo validates **5 architectural invariants** that define the Universal Player Architecture:
24
+
25
+ 1. **Actor Authority** - Actor guards control navigation, infrastructure obeys
26
+ 2. **Strict Separation** - Business logic has zero UI framework imports
27
+ 3. **Signal-Only Reactivity** - React rendering driven by signals, not React state
28
+ 4. **Passive Infrastructure** - Components forward events, don't control navigation
29
+ 5. **State-Driven Reset** - Browser back button sends event to actor for validation
30
+
31
+ ### Tech Stack
32
+
33
+ - **Business Logic:** XState v5 state machines (`@xmachines/play-xstate`)
34
+ - **Reactive Substrate:** TC39 Signals (`@xmachines/play-signals`)
35
+ - **Router Integration:** TanStack Router (`@xmachines/play-tanstack-react-router`)
36
+ - **View Renderer:** React 18 (`@xmachines/play-react`)
37
+ - **UI Schema:** Zod catalog (`@xmachines/play-catalog`)
38
+
39
+ ### Project Structure
40
+
41
+ ```
42
+ examples/demo-app/
43
+ ├── src/
44
+ │ ├── machines/ # Business logic (framework-agnostic)
45
+ │ │ ├── auth-machine.ts # XState machine with guards
46
+ │ │ └── catalog.ts # Type-safe component schemas
47
+ │ ├── components/ # React UI (Passive Infrastructure)
48
+ │ │ ├── LoginForm.tsx # Forwards auth.login event
49
+ │ │ └── Dashboard.tsx # Forwards auth.logout event
50
+ │ └── App.tsx # Integration layer (router + renderer)
51
+ ├── test/
52
+ │ └── invariants/ # Automated invariant validation
53
+ └── docs/
54
+ ├── ARCHITECTURE.md # This file
55
+ └── SWAP-REACT.md # Framework swapping guide
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Data Flow
61
+
62
+ ### User Action → State Transition Flow
63
+
64
+ ```
65
+ User clicks "Log In" button
66
+
67
+ LoginForm.tsx forwards event via send() [Passive Infrastructure]
68
+
69
+ actor.send({ type: "auth.login", username, password })
70
+
71
+ XState machine processes event
72
+
73
+ Guard checks if transition is valid [Actor Authority]
74
+
75
+ Context updated: { isAuthenticated: true }
76
+
77
+ State transition: loggedOut → dashboard
78
+
79
+ Signal emits new value
80
+
81
+ useSignalEffect detects change [Signal-Only Reactivity]
82
+
83
+ PlayRenderer triggers React re-render
84
+
85
+ SignalSyncedHistory observes actor.currentRoute signal (derived from state)
86
+
87
+ Browser URL updates to /dashboard [Passive Infrastructure]
88
+
89
+ React renders Dashboard component with new props
90
+ ```
91
+
92
+ **Key Insight:** The actor decides everything. Infrastructure (router, renderer) observes and reacts.
93
+
94
+ ---
95
+
96
+ ## File-by-File Explanation
97
+
98
+ ### 1. `src/machines/auth-machine.ts` - Business Logic Layer
99
+
100
+ **Purpose:** Defines authentication state machine with guards controlling navigation.
101
+
102
+ **Key Code:**
103
+
104
+ ```typescript
105
+ // Invariant: Strict Separation
106
+ // ZERO React or TanStack Router imports - business logic is framework-agnostic
107
+ import { createMachine, assign } from "xstate";
108
+
109
+ interface AuthContext {
110
+ isAuthenticated: boolean;
111
+ username: string | null;
112
+ routeParams: Record<string, string>;
113
+ queryParams: Record<string, string>;
114
+ }
115
+
116
+ type AuthEvent =
117
+ | { type: "play.route"; to: string }
118
+ | { type: "auth.login"; username: string; password: string }
119
+ | { type: "auth.logout" };
120
+
121
+ export const authMachine = createMachine({
122
+ id: "auth",
123
+ initial: "loggedOut",
124
+ context: {
125
+ isAuthenticated: false,
126
+ username: null,
127
+ routeParams: {},
128
+ queryParams: {},
129
+ },
130
+ states: {
131
+ loggedOut: {
132
+ meta: {
133
+ route: "/",
134
+ // Invariant: Strict Separation
135
+ // View structure defined in metadata, not JSX
136
+ view: {
137
+ component: "LoginForm",
138
+ props: { title: "Please Log In" },
139
+ },
140
+ },
141
+ on: {
142
+ "auth.login": {
143
+ target: "loggedIn",
144
+ actions: assign({
145
+ isAuthenticated: true,
146
+ username: ({ event }) => event.username,
147
+ }),
148
+ },
149
+ },
150
+ },
151
+ loggedIn: {
152
+ meta: {
153
+ route: "/dashboard",
154
+ view: {
155
+ component: "Dashboard",
156
+ props: { welcome: true },
157
+ },
158
+ },
159
+ // Invariant: Actor Authority
160
+ // Always-guard validates state entry - can I BE in this state?
161
+ always: [
162
+ {
163
+ target: "loggedOut",
164
+ guard: ({ context }) => !context.isAuthenticated,
165
+ },
166
+ ],
167
+ on: {
168
+ "auth.logout": {
169
+ target: "loggedOut",
170
+ actions: assign({
171
+ isAuthenticated: false,
172
+ username: null,
173
+ }),
174
+ },
175
+ },
176
+ },
177
+ },
178
+ });
179
+ ```
180
+
181
+ **What This Demonstrates:**
182
+
183
+ - **Actor Authority:** Always-guard `({ context }) => !context.isAuthenticated` automatically redirects to loggedOut state when authentication is lost
184
+ - **Strict Separation:** No React imports - state machine is pure business logic
185
+ - **State-Driven Reset:** Machine processes play.route events from browser back/forward for validation
186
+
187
+ **Verification:**
188
+
189
+ ```bash
190
+ # Verify zero React imports
191
+ grep -n "from ['\"']react" src/machines/auth-machine.ts
192
+ # (No output = passes)
193
+ ```
194
+
195
+ ---
196
+
197
+ ### 2. `src/machines/catalog.ts` - Type-Safe UI Schema
198
+
199
+ **Purpose:** Defines component prop schemas using Zod for runtime validation.
200
+
201
+ **Key Code:**
202
+
203
+ ```typescript
204
+ // Invariant: Strict Separation
205
+ // Catalog is framework-agnostic - no React imports
206
+ import { z } from "zod";
207
+ import { defineCatalog } from "@xmachines/play-catalog";
208
+
209
+ export const catalog = defineCatalog({
210
+ LoginForm: z.object({
211
+ title: z.string(),
212
+ }),
213
+ Dashboard: z.object({
214
+ welcome: z.boolean(),
215
+ }),
216
+ });
217
+ ```
218
+
219
+ **What This Demonstrates:**
220
+
221
+ - **Strict Separation:** Catalog has zero React imports - props are inferred by renderer
222
+ - **Type Safety:** Zod schemas provide runtime validation and compile-time type inference
223
+ - **Framework Agnostic:** Same catalog works with React, Vue, Svelte, or any renderer
224
+
225
+ **Verification:**
226
+
227
+ ```bash
228
+ # Verify zero React imports
229
+ grep -n "from ['\"']react" src/machines/catalog.ts
230
+ # (No output = passes)
231
+ ```
232
+
233
+ ---
234
+
235
+ ### 3. `src/components/LoginForm.tsx` - Passive Infrastructure Layer
236
+
237
+ **Purpose:** React component that forwards credentials to actor without controlling navigation.
238
+
239
+ **Key Code:**
240
+
241
+ ```typescript
242
+ import React, { useState, type FormEvent } from "react";
243
+
244
+ interface LoginFormProps {
245
+ send: (event: any) => void;
246
+ title: string;
247
+ }
248
+
249
+ export const LoginForm: React.FC<LoginFormProps> = ({ send, title }) => {
250
+ // Local UI state ONLY (not business logic)
251
+ const [username, setUsername] = useState("");
252
+ const [password, setPassword] = useState("");
253
+
254
+ const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
255
+ e.preventDefault();
256
+
257
+ // Invariant: Passive Infrastructure
258
+ // Forward event to actor, let actor decide validity
259
+ send({
260
+ type: "auth.login",
261
+ username,
262
+ password,
263
+ });
264
+ };
265
+
266
+ return (
267
+ <div>
268
+ <h1>{title}</h1>
269
+ <form onSubmit={handleSubmit}>
270
+ <input
271
+ type="text"
272
+ value={username}
273
+ onChange={(e) => setUsername(e.target.value)}
274
+ required
275
+ />
276
+ <input
277
+ type="password"
278
+ value={password}
279
+ onChange={(e) => setPassword(e.target.value)}
280
+ required
281
+ />
282
+ <button type="submit">Log In</button>
283
+ </form>
284
+ </div>
285
+ );
286
+ };
287
+ ```
288
+
289
+ **What This Demonstrates:**
290
+
291
+ - **Passive Infrastructure:** Component forwards `auth.login` event via `send()`, doesn't control navigation
292
+ - **Signal-Only Reactivity:** Business state comes from actor, not React state (username/password are ephemeral form inputs)
293
+ - **Strict Separation:** Zero router imports - component doesn't know about TanStack Router
294
+
295
+ **Verification:**
296
+
297
+ ```bash
298
+ # Verify zero router imports
299
+ grep -n "@tanstack/react-router" src/components/LoginForm.tsx
300
+ # (No output = passes)
301
+ ```
302
+
303
+ ---
304
+
305
+ ### 4. `src/components/Dashboard.tsx` - Protected Route Component
306
+
307
+ **Purpose:** Displays dashboard content and forwards logout event to actor.
308
+
309
+ **Key Code:**
310
+
311
+ ```typescript
312
+ import React from "react";
313
+
314
+ interface DashboardProps {
315
+ send: (event: any) => void;
316
+ welcome: boolean;
317
+ }
318
+
319
+ export const Dashboard: React.FC<DashboardProps> = ({ send, welcome }) => {
320
+ const handleLogout = () => {
321
+ // Invariant: Passive Infrastructure
322
+ // Forward event to actor
323
+ send({ type: "auth.logout" });
324
+ };
325
+
326
+ return (
327
+ <div>
328
+ {welcome && <h1>Welcome to the Dashboard!</h1>}
329
+ <p>
330
+ This is a protected route - you can only see this because you're logged
331
+ in.
332
+ </p>
333
+ <button onClick={handleLogout}>Logout</button>
334
+ </div>
335
+ );
336
+ };
337
+ ```
338
+
339
+ **What This Demonstrates:**
340
+
341
+ - **Passive Infrastructure:** Component forwards `auth.logout` event, doesn't navigate directly
342
+ - **Actor Authority:** Access control happens in actor guard, not component logic
343
+
344
+ **Verification:**
345
+
346
+ ```bash
347
+ # Verify zero router imports
348
+ grep -n "@tanstack/react-router" src/components/Dashboard.tsx
349
+ # (No output = passes)
350
+ ```
351
+
352
+ ---
353
+
354
+ ### 5. `src/App.tsx` - Integration Layer
355
+
356
+ **Purpose:** Wires together actor, router, and renderer.
357
+
358
+ **Key Code:**
359
+
360
+ ```typescript
361
+ import { definePlayer } from "@xmachines/play-xstate";
362
+ import { createPlayRouter } from "@xmachines/play-tanstack-react-router";
363
+ import { PlayRenderer } from "@xmachines/play-react";
364
+ import { defineComponents } from "@xmachines/play-catalog";
365
+ import { authMachine } from "./machines/auth-machine";
366
+ import { catalog } from "./machines/catalog";
367
+ import { LoginForm } from "./components/LoginForm";
368
+ import { Dashboard } from "./components/Dashboard";
369
+
370
+ // Define component implementations
371
+ const components = defineComponents(catalog, {
372
+ LoginForm,
373
+ Dashboard,
374
+ });
375
+
376
+ // Create player factory
377
+ const createPlayer = definePlayer({
378
+ machine: authMachine,
379
+ catalog,
380
+ });
381
+
382
+ function App() {
383
+ // Create actor instance
384
+ const actor = createPlayer();
385
+
386
+ // Start actor
387
+ actor.start();
388
+
389
+ // Create router that syncs with actor state
390
+ // Invariant: Passive Infrastructure
391
+ // Router observes actor state, sends play.route events on browser back/forward
392
+ const router = createPlayRouter({
393
+ actor,
394
+ });
395
+
396
+ return (
397
+ <>
398
+ {/* Invariant: Signal-Only Reactivity */}
399
+ {/* PlayRenderer uses useSignalEffect to observe actor signals */}
400
+ <PlayRenderer actor={actor} components={components} />
401
+ </>
402
+ );
403
+ }
404
+
405
+ export default App;
406
+ ```
407
+
408
+ **What This Demonstrates:**
409
+
410
+ - **Signal-Only Reactivity:** `PlayRenderer` uses `useSignalEffect` to observe actor signals
411
+ - **Passive Infrastructure:** Router created with `createPlayRouter({ actor })` - router observes, doesn't control
412
+ - **Actor Authority:** Actor is the single source of truth - router and renderer react to actor state
413
+
414
+ **Flow:**
415
+
416
+ 1. `createPlayer()` creates actor factory from machine + catalog
417
+ 2. `actor.start()` initializes state machine
418
+ 3. `createPlayRouter({ actor })` creates `SignalSyncedHistory` that:
419
+ - Observes `actor.state` signal via `Signal.subtle.Watcher`
420
+ - Updates browser URL when actor changes state
421
+ - Sends `play.route` event to actor when browser back/forward pressed
422
+ 4. `PlayRenderer` observes `actor.state` signal and renders matching component
423
+
424
+ ---
425
+
426
+ ## Key Patterns
427
+
428
+ ### Pattern 1: Actor Authority (Guard-Based Navigation)
429
+
430
+ **Code Location:** `src/machines/auth-machine.ts`
431
+
432
+ ```typescript
433
+ loggedIn: {
434
+ meta: { route: "/dashboard" },
435
+ // Always-guard validates state entry
436
+ always: [
437
+ {
438
+ target: "loggedOut",
439
+ guard: ({ context }) => !context.isAuthenticated
440
+ }
441
+ ]
442
+ }
443
+ ```
444
+
445
+ **How It Works:**
446
+
447
+ - Always-guard checks: "Can I BE in this state given current context?"
448
+ - If `!isAuthenticated`, automatically transitions to loggedOut
449
+ - Guards validate state invariants, not event properties
450
+ - See [demo-app/auth-machine.ts](../src/machines/auth-machine.ts) for full implementation using `formatPlayRouteTransitions`
451
+
452
+ **Test:** `test/invariants/actor-authority.test.ts` - Attempts to navigate to `/dashboard` while logged out, verifies guard rejects
453
+
454
+ ---
455
+
456
+ ### Pattern 2: meta.view Definition (Strict Separation)
457
+
458
+ **Code Location:** `src/machines/auth-machine.ts` lines 55-63
459
+
460
+ ```typescript
461
+ meta: {
462
+ route: "/",
463
+ view: {
464
+ component: "LoginForm",
465
+ props: { title: "Please Log In" },
466
+ },
467
+ }
468
+ ```
469
+
470
+ **How It Works:**
471
+
472
+ - View structure defined declaratively in state machine metadata
473
+ - Component name is a string reference (not JSX import)
474
+ - Props are plain data (not React elements)
475
+ - Renderer resolves component name to actual component via catalog
476
+
477
+ **Test:** `test/invariants/strict-separation.test.ts` - Verifies zero React imports in `auth-machine.ts` and `catalog.ts`
478
+
479
+ ---
480
+
481
+ ### Pattern 3: Signal Observation (Signal-Only Reactivity)
482
+
483
+ **Code Location:** `packages/play-react/src/PlayRenderer.tsx` (simplified)
484
+
485
+ ```typescript
486
+ function PlayRenderer({ actor, components }) {
487
+ const [, setTick] = useState(0);
488
+
489
+ useSignalEffect(() => {
490
+ // Observe actor.state signal
491
+ const currentState = actor.state.get();
492
+ // Trigger React re-render
493
+ setTick((t) => t + 1);
494
+ });
495
+
496
+ const currentState = actor.state.get();
497
+ const view = currentState.meta?.view;
498
+ const Component = components[view.component];
499
+
500
+ return <Component send={actor.send.bind(actor)} {...view.props} />;
501
+ }
502
+ ```
503
+
504
+ **How It Works:**
505
+
506
+ - `useSignalEffect` uses `Signal.subtle.Watcher` to observe `actor.state` signal
507
+ - When signal changes, React is triggered via `setTick`
508
+ - Business state lives in actor, not React state
509
+
510
+ **Test:** `test/invariants/signal-only.test.ts` - Verifies `PlayRenderer` uses `useSignalEffect`
511
+
512
+ ---
513
+
514
+ ### Pattern 4: Event Forwarding (Passive Infrastructure)
515
+
516
+ **Code Location:** `src/components/LoginForm.tsx` lines 54-64
517
+
518
+ ```typescript
519
+ const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
520
+ e.preventDefault();
521
+ send({ type: "auth.login", username, password });
522
+ };
523
+ ```
524
+
525
+ **How It Works:**
526
+
527
+ - Component receives `send` function via props (bound to actor)
528
+ - Component forwards event to actor without knowing actor internals
529
+ - Actor processes event and decides outcome
530
+
531
+ **Test:** `test/invariants/passive-infra.test.ts` - Verifies components have zero router imports, use `send()` for events
532
+
533
+ ---
534
+
535
+ ### Pattern 5: Navigation Handling (State-Driven Reset)
536
+
537
+ **Code Location:** `packages/play-tanstack-react-router/src/SignalSyncedHistory.ts` (simplified)
538
+
539
+ ```typescript
540
+ class SignalSyncedHistory {
541
+ constructor(actor) {
542
+ // Listen to browser popstate (back/forward)
543
+ window.addEventListener("popstate", () => {
544
+ const newPath = window.location.pathname;
545
+ // Send event to actor for validation
546
+ actor.send({ type: "play.route", to: newPath });
547
+ });
548
+
549
+ // Observe actor's currentRoute signal (derived from state)
550
+ const watcher = new Signal.subtle.Watcher(() => {
551
+ const route = actor.currentRoute.get();
552
+ // Update browser URL if actor changed route
553
+ if (route) window.history.pushState({}, "", route);
554
+ });
555
+ }
556
+ }
557
+ ```
558
+
559
+ **How It Works:**
560
+
561
+ - Browser back/forward triggers `popstate` event
562
+ - SignalSyncedHistory sends `play.route` event to actor
563
+ - Actor processes event - always-guards validate state entry
564
+ - If guards allow, state updates and signal emits
565
+ - SignalSyncedHistory observes signal and updates browser URL
566
+
567
+ **Test:** `test/invariants/state-driven.test.ts` - Sends `play.route` event to simulate browser back, verifies actor processes it
568
+
569
+ ---
570
+
571
+ ## Verification
572
+
573
+ ### Automated Tests
574
+
575
+ Run all invariant validation tests:
576
+
577
+ ```bash
578
+ cd examples/demo-app
579
+ npm test
580
+ ```
581
+
582
+ Expected output:
583
+
584
+ ```
585
+ ✔ DEMO-02: Actor Authority - Guards reject invalid navigation
586
+ ✔ DEMO-03: Strict Separation - Business logic has zero React imports
587
+ ✔ DEMO-04: State-Driven Reset - Browser back sends event to actor
588
+ ✔ DEMO-05: Passive Infrastructure - Components never import TanStack Router
589
+ ✔ DEMO-06: Signal-Only Reactivity - PlayRenderer uses signals not React state
590
+
591
+ ℹ tests 5
592
+ ℹ pass 5
593
+ ℹ fail 0
594
+ ```
595
+
596
+ ### Manual Verification
597
+
598
+ 1. **Actor Authority:**
599
+
600
+ ```bash
601
+ npm run dev
602
+ # Visit http://localhost:3000/dashboard directly (while logged out)
603
+ # Verify: URL stays at / or redirects (guard rejected)
604
+ ```
605
+
606
+ 2. **Strict Separation:**
607
+
608
+ ```bash
609
+ grep -rn "from ['\"']react" src/machines/
610
+ # Expected: no output (zero React imports)
611
+ ```
612
+
613
+ 3. **Passive Infrastructure:**
614
+
615
+ ```bash
616
+ grep -rn "@tanstack/react-router" src/components/
617
+ # Expected: no output (zero router imports)
618
+ ```
619
+
620
+ 4. **Signal-Only Reactivity:**
621
+
622
+ ```bash
623
+ grep -n "useSignalEffect" packages/play-react/src/PlayRenderer.tsx
624
+ # Expected: line number showing useSignalEffect usage
625
+ ```
626
+
627
+ 5. **State-Driven Reset:**
628
+ ```bash
629
+ # Run demo, login, click browser back button
630
+ # Observe: actor.send({ type: "play.route", to: "/" }) logged
631
+ ```
632
+
633
+ ---
634
+
635
+ ## Next Steps
636
+
637
+ - **Understand Invariants:** Read the RFC specification for detailed explanations
638
+ - **Swap Frameworks:** Read [SWAP-REACT.md](./SWAP-REACT.md) to see how to replace React with Vue/Svelte
639
+ - **Build Your Own:** Use this demo as a template for your application
640
+
641
+ ---
642
+
643
+ **Questions?** See the RFC specification for deeper invariant explanations and rationale.