@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,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Navigation Component - Route Discovery UI
|
|
3
|
+
*
|
|
4
|
+
* Displays all available routes organized by category (Public, Protected, Auth).
|
|
5
|
+
* Demonstrates how UI can help users discover routes without hardcoding paths.
|
|
6
|
+
*
|
|
7
|
+
* @invariant Passive Infrastructure - Forwards events to actor, doesn't control navigation
|
|
8
|
+
* @invariant Strict Separation - Zero router imports
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
interface Route {
|
|
12
|
+
path: string;
|
|
13
|
+
label: string;
|
|
14
|
+
description: string;
|
|
15
|
+
requiresAuth: boolean;
|
|
16
|
+
authOnly: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
import type { InferComponentProps } from "@xmachines/play-catalog";
|
|
20
|
+
import type { catalog } from "@xmachines/play-router-shared";
|
|
21
|
+
|
|
22
|
+
type NavigationProps = InferComponentProps<typeof catalog, "Navigation"> & {
|
|
23
|
+
send: (event: any) => void;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* All routes in the application
|
|
28
|
+
* Organized by access level for user discovery
|
|
29
|
+
*/
|
|
30
|
+
const ROUTES: Route[] = [
|
|
31
|
+
// Public routes (accessible anytime)
|
|
32
|
+
{
|
|
33
|
+
path: "/",
|
|
34
|
+
label: "Home",
|
|
35
|
+
description: "Landing page / Dashboard (when logged in)",
|
|
36
|
+
requiresAuth: false,
|
|
37
|
+
authOnly: false,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
path: "/about",
|
|
41
|
+
label: "About",
|
|
42
|
+
description: "Project information",
|
|
43
|
+
requiresAuth: false,
|
|
44
|
+
authOnly: false,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
path: "/contact",
|
|
48
|
+
label: "Contact",
|
|
49
|
+
description: "Get in touch",
|
|
50
|
+
requiresAuth: false,
|
|
51
|
+
authOnly: false,
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
// Protected routes (require authentication)
|
|
55
|
+
{
|
|
56
|
+
path: "/settings",
|
|
57
|
+
label: "Settings",
|
|
58
|
+
description: "Account settings",
|
|
59
|
+
requiresAuth: true,
|
|
60
|
+
authOnly: false,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
path: "/settings/profile",
|
|
64
|
+
label: "Settings › Profile",
|
|
65
|
+
description: "Profile settings section",
|
|
66
|
+
requiresAuth: true,
|
|
67
|
+
authOnly: false,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
path: "/profile/demo-user",
|
|
71
|
+
label: "Profile",
|
|
72
|
+
description: "User profile (example)",
|
|
73
|
+
requiresAuth: true,
|
|
74
|
+
authOnly: false,
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
// Auth-only routes (only when logged out)
|
|
78
|
+
{
|
|
79
|
+
path: "/login",
|
|
80
|
+
label: "Login",
|
|
81
|
+
description: "Sign in to your account",
|
|
82
|
+
requiresAuth: false,
|
|
83
|
+
authOnly: true,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
path: "/register",
|
|
87
|
+
label: "Register",
|
|
88
|
+
description: "Create new account",
|
|
89
|
+
requiresAuth: false,
|
|
90
|
+
authOnly: true,
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Navigation component with route discovery
|
|
96
|
+
*
|
|
97
|
+
* Displays routes categorized by access level:
|
|
98
|
+
* - Public: Always available
|
|
99
|
+
* - Protected: Visible but disabled when logged out
|
|
100
|
+
* - Auth: Only shown when logged out
|
|
101
|
+
*/
|
|
102
|
+
export function Navigation({ send, isAuthenticated, currentPath }: NavigationProps) {
|
|
103
|
+
// Filter routes by category
|
|
104
|
+
const publicRoutes = ROUTES.filter((r) => !r.requiresAuth && !r.authOnly);
|
|
105
|
+
const protectedRoutes = ROUTES.filter((r) => r.requiresAuth);
|
|
106
|
+
const authRoutes = ROUTES.filter((r) => r.authOnly);
|
|
107
|
+
|
|
108
|
+
const handleNavigate = (path: string) => {
|
|
109
|
+
// Map path to play.route event (unified navigation)
|
|
110
|
+
const eventMap: Record<string, any> = {
|
|
111
|
+
"/": { type: "play.route", to: "#home" },
|
|
112
|
+
"/about": { type: "play.route", to: "#about" },
|
|
113
|
+
"/contact": { type: "play.route", to: "#contact" },
|
|
114
|
+
"/settings": { type: "play.route", to: "#settings" },
|
|
115
|
+
"/settings/profile": {
|
|
116
|
+
type: "play.route",
|
|
117
|
+
to: "#settings",
|
|
118
|
+
params: { section: "profile" },
|
|
119
|
+
},
|
|
120
|
+
"/profile/demo-user": {
|
|
121
|
+
type: "play.route",
|
|
122
|
+
to: "#profile",
|
|
123
|
+
params: { userId: "demo-user" },
|
|
124
|
+
},
|
|
125
|
+
"/login": { type: "play.route", to: "#login" },
|
|
126
|
+
"/register": { type: "play.route", to: "#register" },
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const event = eventMap[path];
|
|
130
|
+
if (event) {
|
|
131
|
+
send(event);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const buttonStyle = (path: string, disabled: boolean = false) => ({
|
|
136
|
+
padding: "10px 15px",
|
|
137
|
+
fontSize: "14px",
|
|
138
|
+
backgroundColor: currentPath === path ? "#0056b3" : "#007bff",
|
|
139
|
+
color: "white",
|
|
140
|
+
border: "none",
|
|
141
|
+
borderRadius: "4px",
|
|
142
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
|
143
|
+
opacity: disabled ? 0.5 : 1,
|
|
144
|
+
transition: "background-color 0.2s",
|
|
145
|
+
textAlign: "left" as const,
|
|
146
|
+
width: "100%",
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const sectionStyle = {
|
|
150
|
+
marginBottom: "20px",
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const routeListStyle = {
|
|
154
|
+
display: "flex",
|
|
155
|
+
flexDirection: "column" as const,
|
|
156
|
+
gap: "8px",
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const routeItemStyle = {
|
|
160
|
+
display: "flex",
|
|
161
|
+
flexDirection: "column" as const,
|
|
162
|
+
gap: "4px",
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const descriptionStyle = {
|
|
166
|
+
fontSize: "12px",
|
|
167
|
+
color: "#6c757d",
|
|
168
|
+
marginLeft: "15px",
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<nav
|
|
173
|
+
style={{
|
|
174
|
+
padding: "20px",
|
|
175
|
+
backgroundColor: "#f8f9fa",
|
|
176
|
+
borderRadius: "8px",
|
|
177
|
+
marginBottom: "20px",
|
|
178
|
+
}}
|
|
179
|
+
>
|
|
180
|
+
<h2 style={{ marginTop: 0, marginBottom: "15px", fontSize: "18px" }}>
|
|
181
|
+
Available Routes
|
|
182
|
+
</h2>
|
|
183
|
+
|
|
184
|
+
{/* Public Routes */}
|
|
185
|
+
<div style={sectionStyle}>
|
|
186
|
+
<h3 style={{ fontSize: "14px", fontWeight: "600", marginBottom: "10px" }}>
|
|
187
|
+
🌍 Public Routes
|
|
188
|
+
</h3>
|
|
189
|
+
<div style={routeListStyle}>
|
|
190
|
+
{publicRoutes.map((route) => (
|
|
191
|
+
<div key={route.path} style={routeItemStyle}>
|
|
192
|
+
<button
|
|
193
|
+
onClick={() => handleNavigate(route.path)}
|
|
194
|
+
style={buttonStyle(route.path)}
|
|
195
|
+
>
|
|
196
|
+
{route.label}
|
|
197
|
+
</button>
|
|
198
|
+
<span style={descriptionStyle}>{route.description}</span>
|
|
199
|
+
</div>
|
|
200
|
+
))}
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
|
|
204
|
+
{/* Protected Routes */}
|
|
205
|
+
<div style={sectionStyle}>
|
|
206
|
+
<h3 style={{ fontSize: "14px", fontWeight: "600", marginBottom: "10px" }}>
|
|
207
|
+
🔒 Protected Routes {!isAuthenticated && "(Login required)"}
|
|
208
|
+
</h3>
|
|
209
|
+
<div style={routeListStyle}>
|
|
210
|
+
{protectedRoutes.map((route) => (
|
|
211
|
+
<div key={route.path} style={routeItemStyle}>
|
|
212
|
+
<button
|
|
213
|
+
onClick={() => handleNavigate(route.path)}
|
|
214
|
+
disabled={!isAuthenticated}
|
|
215
|
+
style={buttonStyle(route.path, !isAuthenticated)}
|
|
216
|
+
>
|
|
217
|
+
{route.label}
|
|
218
|
+
{!isAuthenticated && " 🔒"}
|
|
219
|
+
</button>
|
|
220
|
+
<span style={descriptionStyle}>{route.description}</span>
|
|
221
|
+
</div>
|
|
222
|
+
))}
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
|
|
226
|
+
{/* Auth-Only Routes */}
|
|
227
|
+
{!isAuthenticated && (
|
|
228
|
+
<div style={sectionStyle}>
|
|
229
|
+
<h3 style={{ fontSize: "14px", fontWeight: "600", marginBottom: "10px" }}>
|
|
230
|
+
🔑 Authentication
|
|
231
|
+
</h3>
|
|
232
|
+
<div style={routeListStyle}>
|
|
233
|
+
{authRoutes.map((route) => (
|
|
234
|
+
<div key={route.path} style={routeItemStyle}>
|
|
235
|
+
<button
|
|
236
|
+
onClick={() => handleNavigate(route.path)}
|
|
237
|
+
style={buttonStyle(route.path)}
|
|
238
|
+
>
|
|
239
|
+
{route.label}
|
|
240
|
+
</button>
|
|
241
|
+
<span style={descriptionStyle}>{route.description}</span>
|
|
242
|
+
</div>
|
|
243
|
+
))}
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
)}
|
|
247
|
+
|
|
248
|
+
{/* Status indicator */}
|
|
249
|
+
<div
|
|
250
|
+
style={{
|
|
251
|
+
marginTop: "20px",
|
|
252
|
+
padding: "10px",
|
|
253
|
+
backgroundColor: isAuthenticated ? "#d4edda" : "#fff3cd",
|
|
254
|
+
borderRadius: "4px",
|
|
255
|
+
fontSize: "14px",
|
|
256
|
+
}}
|
|
257
|
+
>
|
|
258
|
+
<strong>Status:</strong> {isAuthenticated ? "✓ Authenticated" : "Not authenticated"}
|
|
259
|
+
</div>
|
|
260
|
+
</nav>
|
|
261
|
+
);
|
|
262
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profile - Protected user profile page with required userId parameter
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates:
|
|
5
|
+
* - Protected route (requires authentication)
|
|
6
|
+
* - Required route parameter (userId)
|
|
7
|
+
* - Parameter validation (guard checks userId exists)
|
|
8
|
+
*
|
|
9
|
+
* @invariant Passive Infrastructure - Forwards events to actor, doesn't control navigation
|
|
10
|
+
* @invariant Strict Separation - Zero router imports
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { InferComponentProps } from "@xmachines/play-catalog";
|
|
14
|
+
import type { catalog } from "@xmachines/play-router-shared";
|
|
15
|
+
|
|
16
|
+
type ProfileProps = InferComponentProps<typeof catalog, "Profile"> & {
|
|
17
|
+
send: (event: any) => void;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function Profile({ username, send }: ProfileProps) {
|
|
21
|
+
return (
|
|
22
|
+
<div
|
|
23
|
+
className="profile"
|
|
24
|
+
style={{ maxWidth: "800px", margin: "50px auto", padding: "20px" }}
|
|
25
|
+
>
|
|
26
|
+
<h1>Profile: {username}</h1>
|
|
27
|
+
<p style={{ fontSize: "16px", lineHeight: "1.6", marginBottom: "20px" }}>
|
|
28
|
+
This is your profile page.
|
|
29
|
+
</p>
|
|
30
|
+
<button
|
|
31
|
+
onClick={() => send({ type: "play.route", to: "#dashboard" })}
|
|
32
|
+
style={{
|
|
33
|
+
padding: "10px 20px",
|
|
34
|
+
fontSize: "16px",
|
|
35
|
+
backgroundColor: "#6c757d",
|
|
36
|
+
color: "white",
|
|
37
|
+
border: "none",
|
|
38
|
+
borderRadius: "4px",
|
|
39
|
+
cursor: "pointer",
|
|
40
|
+
}}
|
|
41
|
+
>
|
|
42
|
+
Back to Dashboard
|
|
43
|
+
</button>
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Register - Auth-only registration form
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates:
|
|
5
|
+
* - Auth-only route (redirects to dashboard if already authenticated)
|
|
6
|
+
* - Form submission sending machine event
|
|
7
|
+
*
|
|
8
|
+
* @invariant Passive Infrastructure - Forwards events to actor, doesn't control navigation
|
|
9
|
+
* @invariant Strict Separation - Zero router imports
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { type FormEvent } from "react";
|
|
13
|
+
import type { InferComponentProps } from "@xmachines/play-catalog";
|
|
14
|
+
import type { catalog } from "@xmachines/play-router-shared";
|
|
15
|
+
|
|
16
|
+
type RegisterProps = InferComponentProps<typeof catalog, "Register"> & {
|
|
17
|
+
send: (event: any) => void;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function Register({ title, send }: RegisterProps) {
|
|
21
|
+
const handleRegister = (e: FormEvent<HTMLFormElement>) => {
|
|
22
|
+
e.preventDefault();
|
|
23
|
+
const formData = new FormData(e.currentTarget);
|
|
24
|
+
send({
|
|
25
|
+
type: "auth.register",
|
|
26
|
+
username: formData.get("username") as string,
|
|
27
|
+
email: formData.get("email") as string,
|
|
28
|
+
password: formData.get("password") as string,
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div
|
|
34
|
+
className="register"
|
|
35
|
+
style={{ maxWidth: "400px", margin: "50px auto", padding: "20px" }}
|
|
36
|
+
>
|
|
37
|
+
<h1>{title}</h1>
|
|
38
|
+
<form onSubmit={handleRegister}>
|
|
39
|
+
<div style={{ marginBottom: "15px" }}>
|
|
40
|
+
<label htmlFor="username" style={{ display: "block", marginBottom: "5px" }}>
|
|
41
|
+
Username
|
|
42
|
+
</label>
|
|
43
|
+
<input
|
|
44
|
+
type="text"
|
|
45
|
+
name="username"
|
|
46
|
+
id="username"
|
|
47
|
+
required
|
|
48
|
+
style={{ width: "100%", padding: "8px", fontSize: "16px" }}
|
|
49
|
+
/>
|
|
50
|
+
</div>
|
|
51
|
+
<div style={{ marginBottom: "15px" }}>
|
|
52
|
+
<label htmlFor="email" style={{ display: "block", marginBottom: "5px" }}>
|
|
53
|
+
Email
|
|
54
|
+
</label>
|
|
55
|
+
<input
|
|
56
|
+
type="email"
|
|
57
|
+
name="email"
|
|
58
|
+
id="email"
|
|
59
|
+
required
|
|
60
|
+
style={{ width: "100%", padding: "8px", fontSize: "16px" }}
|
|
61
|
+
/>
|
|
62
|
+
</div>
|
|
63
|
+
<div style={{ marginBottom: "15px" }}>
|
|
64
|
+
<label htmlFor="password" style={{ display: "block", marginBottom: "5px" }}>
|
|
65
|
+
Password
|
|
66
|
+
</label>
|
|
67
|
+
<input
|
|
68
|
+
type="password"
|
|
69
|
+
name="password"
|
|
70
|
+
id="password"
|
|
71
|
+
required
|
|
72
|
+
style={{ width: "100%", padding: "8px", fontSize: "16px" }}
|
|
73
|
+
/>
|
|
74
|
+
</div>
|
|
75
|
+
<button
|
|
76
|
+
type="submit"
|
|
77
|
+
style={{
|
|
78
|
+
width: "100%",
|
|
79
|
+
padding: "10px",
|
|
80
|
+
fontSize: "16px",
|
|
81
|
+
backgroundColor: "#28a745",
|
|
82
|
+
color: "white",
|
|
83
|
+
border: "none",
|
|
84
|
+
borderRadius: "4px",
|
|
85
|
+
cursor: "pointer",
|
|
86
|
+
marginBottom: "15px",
|
|
87
|
+
}}
|
|
88
|
+
>
|
|
89
|
+
Register
|
|
90
|
+
</button>
|
|
91
|
+
</form>
|
|
92
|
+
<button
|
|
93
|
+
onClick={() => send({ type: "play.route", to: "#login" })}
|
|
94
|
+
style={{
|
|
95
|
+
width: "100%",
|
|
96
|
+
padding: "10px",
|
|
97
|
+
fontSize: "16px",
|
|
98
|
+
backgroundColor: "#6c757d",
|
|
99
|
+
color: "white",
|
|
100
|
+
border: "none",
|
|
101
|
+
borderRadius: "4px",
|
|
102
|
+
cursor: "pointer",
|
|
103
|
+
}}
|
|
104
|
+
>
|
|
105
|
+
Back to Login
|
|
106
|
+
</button>
|
|
107
|
+
</div>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings - Protected settings page with optional section parameter
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates:
|
|
5
|
+
* - Protected route (requires authentication)
|
|
6
|
+
* - Optional route parameter (section)
|
|
7
|
+
* - Parameter-driven content display
|
|
8
|
+
*
|
|
9
|
+
* @invariant Passive Infrastructure - Forwards events to actor, doesn't control navigation
|
|
10
|
+
* @invariant Strict Separation - Zero router imports
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { InferComponentProps } from "@xmachines/play-catalog";
|
|
14
|
+
import type { catalog } from "@xmachines/play-router-shared";
|
|
15
|
+
|
|
16
|
+
type SettingsProps = InferComponentProps<typeof catalog, "Settings"> & {
|
|
17
|
+
send: (event: any) => void;
|
|
18
|
+
routeParams?: Record<string, string>;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export function Settings({ section, username, send, routeParams }: SettingsProps) {
|
|
22
|
+
// Use section from route params (dynamic from URL) with fallback to prop or "general"
|
|
23
|
+
const actualSection = routeParams?.section || section || "general";
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div
|
|
27
|
+
className="settings"
|
|
28
|
+
style={{ maxWidth: "800px", margin: "50px auto", padding: "20px" }}
|
|
29
|
+
>
|
|
30
|
+
<h1>Settings - {username}</h1>
|
|
31
|
+
<p style={{ fontSize: "16px", lineHeight: "1.6", marginBottom: "20px" }}>
|
|
32
|
+
Current section: {actualSection}
|
|
33
|
+
</p>
|
|
34
|
+
<nav style={{ display: "flex", gap: "10px", marginBottom: "30px" }}>
|
|
35
|
+
<button
|
|
36
|
+
onClick={() =>
|
|
37
|
+
send({
|
|
38
|
+
type: "play.route",
|
|
39
|
+
to: "#settings",
|
|
40
|
+
params: { section: "account" },
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
style={{
|
|
44
|
+
padding: "10px 20px",
|
|
45
|
+
fontSize: "16px",
|
|
46
|
+
backgroundColor: "#007bff",
|
|
47
|
+
color: "white",
|
|
48
|
+
border: "none",
|
|
49
|
+
borderRadius: "4px",
|
|
50
|
+
cursor: "pointer",
|
|
51
|
+
}}
|
|
52
|
+
>
|
|
53
|
+
Account
|
|
54
|
+
</button>
|
|
55
|
+
<button
|
|
56
|
+
onClick={() =>
|
|
57
|
+
send({
|
|
58
|
+
type: "play.route",
|
|
59
|
+
to: "#settings",
|
|
60
|
+
params: { section: "privacy" },
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
style={{
|
|
64
|
+
padding: "10px 20px",
|
|
65
|
+
fontSize: "16px",
|
|
66
|
+
backgroundColor: "#007bff",
|
|
67
|
+
color: "white",
|
|
68
|
+
border: "none",
|
|
69
|
+
borderRadius: "4px",
|
|
70
|
+
cursor: "pointer",
|
|
71
|
+
}}
|
|
72
|
+
>
|
|
73
|
+
Privacy
|
|
74
|
+
</button>
|
|
75
|
+
</nav>
|
|
76
|
+
<button
|
|
77
|
+
onClick={() => send({ type: "play.route", to: "#home" })}
|
|
78
|
+
style={{
|
|
79
|
+
padding: "10px 20px",
|
|
80
|
+
fontSize: "16px",
|
|
81
|
+
backgroundColor: "#6c757d",
|
|
82
|
+
color: "white",
|
|
83
|
+
border: "none",
|
|
84
|
+
borderRadius: "4px",
|
|
85
|
+
cursor: "pointer",
|
|
86
|
+
}}
|
|
87
|
+
>
|
|
88
|
+
Back to Home
|
|
89
|
+
</button>
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component Exports
|
|
3
|
+
*
|
|
4
|
+
* Centralized exports for all demo app components.
|
|
5
|
+
* Components are framework-agnostic and receive props from PlayRenderer.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { About } from "./About.js";
|
|
9
|
+
export { Contact } from "./Contact.js";
|
|
10
|
+
export { Dashboard } from "./Dashboard.js";
|
|
11
|
+
export { Home } from "./Home.js";
|
|
12
|
+
export { Login } from "./Login.js";
|
|
13
|
+
export { Navigation } from "./Navigation.js";
|
|
14
|
+
export { Profile } from "./Profile.js";
|
|
15
|
+
export { Register } from "./Register.js";
|
|
16
|
+
export { Settings } from "./Settings.js";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application Entry Point
|
|
3
|
+
*
|
|
4
|
+
* Standard Vite + React entry point.
|
|
5
|
+
* Mounts the App component into the DOM root element.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import ReactDOM from "react-dom/client";
|
|
9
|
+
import App from "./App.js";
|
|
10
|
+
|
|
11
|
+
// Get root element from HTML
|
|
12
|
+
const rootElement = document.getElementById("root");
|
|
13
|
+
|
|
14
|
+
if (!rootElement) {
|
|
15
|
+
throw new Error("Root element not found. Check index.html has <div id='root'>");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Create React root and render app
|
|
19
|
+
// Note: StrictMode disabled during development to avoid double-render issues with actors
|
|
20
|
+
ReactDOM.createRoot(rootElement).render(<App />);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DEMO-02: Actor Authority Invariant Test
|
|
3
|
+
*
|
|
4
|
+
* Validates that Actor controls navigation via guards (Actor Authority).
|
|
5
|
+
* Guards reject invalid navigation - infrastructure doesn't decide.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { test } from "vitest";
|
|
9
|
+
import { expect } from "vitest";
|
|
10
|
+
import { definePlayer } from "@xmachines/play-xstate";
|
|
11
|
+
import { authMachine, catalog } from "@xmachines/play-router-shared";
|
|
12
|
+
|
|
13
|
+
test("DEMO-02: Actor Authority - Guards reject invalid navigation", async () => {
|
|
14
|
+
// Provide catalog so view-binding validation uses real component vocabulary
|
|
15
|
+
const createPlayer = definePlayer({ machine: authMachine, catalog });
|
|
16
|
+
const actor = createPlayer();
|
|
17
|
+
actor.start();
|
|
18
|
+
|
|
19
|
+
// Initial state: home (not authenticated)
|
|
20
|
+
expect(actor.getSnapshot().matches("home")).toBe(true);
|
|
21
|
+
expect(actor.currentRoute.get()).toBe("/");
|
|
22
|
+
|
|
23
|
+
// Navigate to login page
|
|
24
|
+
actor.send({ type: "play.route", to: "#login" });
|
|
25
|
+
expect(actor.getSnapshot().matches("login")).toBe(true);
|
|
26
|
+
|
|
27
|
+
// Attempt to navigate back to / (which should stay in home when not authenticated)
|
|
28
|
+
actor.send({ type: "play.route", to: "#home" });
|
|
29
|
+
|
|
30
|
+
// Should go to home state
|
|
31
|
+
expect(actor.getSnapshot().matches("home")).toBe(true);
|
|
32
|
+
expect(actor.currentRoute.get()).toBe("/");
|
|
33
|
+
|
|
34
|
+
// Login successfully
|
|
35
|
+
actor.send({ type: "auth.login", username: "test", password: "pass" });
|
|
36
|
+
|
|
37
|
+
// Now in dashboard state with protected route
|
|
38
|
+
expect(actor.getSnapshot().matches("dashboard")).toBe(true);
|
|
39
|
+
expect(actor.currentRoute.get()).toBe("/dashboard");
|
|
40
|
+
expect(actor.getSnapshot().context.username).toBe("test");
|
|
41
|
+
|
|
42
|
+
// Can now navigate to protected routes
|
|
43
|
+
actor.send({ type: "play.route", to: "#settings" });
|
|
44
|
+
expect(actor.getSnapshot().matches("settings")).toBe(true);
|
|
45
|
+
|
|
46
|
+
actor.stop();
|
|
47
|
+
|
|
48
|
+
// Allow microtasks to complete to prevent async activity warnings
|
|
49
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
50
|
+
});
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|