diff --git a/examples/platformer/package.json b/examples/platformer/package.json
index a885a3d..b0fed9e 100644
--- a/examples/platformer/package.json
+++ b/examples/platformer/package.json
@@ -6,7 +6,8 @@
"type": "module",
"scripts": {
"dev": "vite",
- "build": "tsc --noEmit && vite build",
+ "typecheck": "tsc --noEmit",
+ "build": "pnpm typecheck && vite build",
"preview": "vite preview",
"check": "pnpm build"
},
diff --git a/examples/platformer/src/machine.ts b/examples/platformer/src/machine.ts
index 1ef6358..1315313 100644
--- a/examples/platformer/src/machine.ts
+++ b/examples/platformer/src/machine.ts
@@ -137,8 +137,8 @@ const initialCharacter = () =>
export const CharacterMachine = Machine.make({
id: "PlatformerCharacter",
states: CharacterStates.states,
- events: Object.values(Event.cases),
- internalEvents: Object.values(InternalEvent.cases),
+ events: [Event],
+ internalEvents: [InternalEvent],
initial: initialCharacter
}).handle({
Character: {
@@ -151,18 +151,29 @@ export const CharacterMachine = Machine.make({
Grounded: {
on: {
JumpPressed: ({ event, target }) =>
- target.branch.Character.locomotion.Airborne(
- State.cases.Airborne.make({ originY: event.y }),
- (airborne) =>
- airborne
- .motion(State.cases.Motion.make({}), (motion) =>
- motion.Jumping(
- State.cases.Jumping.make({ startedAt: event.at, push: 0, kind: "Ground" })
- )
- )
- .airJump(State.cases.AirJump.make({}), (airJump) =>
- airJump.AirJumpGroundLock(State.cases.AirJumpGroundLock.make({}))
+ target.full.Character(State.cases.Character.make({}), (character) =>
+ character
+ .locomotion(State.cases.Locomotion.make({}), (locomotion) =>
+ locomotion.Airborne(State.cases.Airborne.make({ originY: event.y }), (airborne) =>
+ airborne
+ .motion(State.cases.Motion.make({}), (motion) =>
+ motion.Jumping(
+ State.cases.Jumping.make({ startedAt: event.at, push: 0, kind: "Ground" })
+ )
+ )
+ .airJump(State.cases.AirJump.make({}), (airJump) =>
+ airJump.AirJumpGroundLock(State.cases.AirJumpGroundLock.make({}))
+ )
)
+ )
+ .facing(State.cases.Facing.make({}), (facing) => facing.Right(State.cases.Right.make({})))
+ .contact(State.cases.WallContact.make({}), (contact) =>
+ event.wall === -1
+ ? contact.LeftWall(State.cases.LeftWall.make({}))
+ : event.wall === 1
+ ? contact.RightWall(State.cases.RightWall.make({}))
+ : contact.NoWall(State.cases.NoWall.make({}))
+ )
)
},
states: {
diff --git a/examples/playground/.gitignore b/examples/playground/.gitignore
new file mode 100644
index 0000000..1502ec2
--- /dev/null
+++ b/examples/playground/.gitignore
@@ -0,0 +1,3 @@
+dist
+node_modules
+
diff --git a/examples/playground/README.md b/examples/playground/README.md
new file mode 100644
index 0000000..7b2b06a
--- /dev/null
+++ b/examples/playground/README.md
@@ -0,0 +1,45 @@
+# Effect Machine examples playground
+
+A React and Vite workspace with TanStack Router routes for implementing small
+`@typeonce/effect-machine` examples.
+
+## Run it
+
+From this directory:
+
+```sh
+pnpm install
+pnpm dev
+```
+
+Build and type-check with:
+
+```sh
+pnpm check
+```
+
+## Starters
+
+Each starter keeps its machine definition next to its route component:
+
+- `src/examples/turnstile`
+- `src/examples/traffic-light`
+- `src/examples/microwave`
+- `src/examples/media-player`
+- `src/examples/worker-tabs`
+
+The first four route components are intentionally light: their domain schemas,
+events, and state topology are ready, while the React-to-machine adapter is left
+for the exercise.
+
+The workers route additionally contains:
+
+- `machine.worker.ts`: Vite's module-worker entry point and the place to start
+ the machine runtime.
+- `worker-client.ts`: typed worker construction, send, subscribe, and cleanup.
+- `tab-channel.ts`: a typed `BroadcastChannel` wrapper for cross-tab messages.
+- `protocol.ts`: worker and tab message boundaries.
+
+Vite discovers the worker because `worker-client.ts` constructs it with
+`new Worker(new URL("./machine.worker.ts", import.meta.url), { type: "module" })`.
+No extra Vite worker plugin is required.
diff --git a/examples/playground/index.html b/examples/playground/index.html
new file mode 100644
index 0000000..1817520
--- /dev/null
+++ b/examples/playground/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ Effect Machine examples
+
+
+
+
+
+
+
diff --git a/examples/playground/package.json b/examples/playground/package.json
new file mode 100644
index 0000000..0b35521
--- /dev/null
+++ b/examples/playground/package.json
@@ -0,0 +1,34 @@
+{
+ "name": "@typeonce/effect-machine-example-playground",
+ "version": "0.0.0",
+ "private": true,
+ "description": "React playground for building @typeonce/effect-machine examples",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "typecheck": "tsc --noEmit",
+ "build": "pnpm typecheck && vite build",
+ "preview": "vite preview",
+ "check": "pnpm build"
+ },
+ "dependencies": {
+ "@effect/atom-react": "4.0.0-beta.102",
+ "@tanstack/react-router": "1.170.18",
+ "@typeonce/effect-machine": "file:../..",
+ "effect": "4.0.0-beta.102",
+ "react": "19.2.7",
+ "react-dom": "19.2.7",
+ "scheduler": "0.27.0"
+ },
+ "devDependencies": {
+ "@types/react": "19.2.17",
+ "@types/react-dom": "19.2.2",
+ "@vitejs/plugin-react": "6.0.4",
+ "typescript": "6.0.3",
+ "vite": "8.1.5"
+ },
+ "packageManager": "pnpm@10.17.1",
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+}
diff --git a/examples/playground/pnpm-lock.yaml b/examples/playground/pnpm-lock.yaml
new file mode 100644
index 0000000..bfe4277
--- /dev/null
+++ b/examples/playground/pnpm-lock.yaml
@@ -0,0 +1,865 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+overrides:
+ effect: 4.0.0-beta.102
+
+importers:
+
+ .:
+ dependencies:
+ '@effect/atom-react':
+ specifier: 4.0.0-beta.102
+ version: 4.0.0-beta.102(effect@4.0.0-beta.102)(react@19.2.7)(scheduler@0.27.0)
+ '@tanstack/react-router':
+ specifier: 1.170.18
+ version: 1.170.18(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@typeonce/effect-machine':
+ specifier: file:../..
+ version: file:../..(effect@4.0.0-beta.102)
+ effect:
+ specifier: 4.0.0-beta.102
+ version: 4.0.0-beta.102
+ react:
+ specifier: 19.2.7
+ version: 19.2.7
+ react-dom:
+ specifier: 19.2.7
+ version: 19.2.7(react@19.2.7)
+ scheduler:
+ specifier: 0.27.0
+ version: 0.27.0
+ devDependencies:
+ '@types/react':
+ specifier: 19.2.17
+ version: 19.2.17
+ '@types/react-dom':
+ specifier: 19.2.2
+ version: 19.2.2(@types/react@19.2.17)
+ '@vitejs/plugin-react':
+ specifier: 6.0.4
+ version: 6.0.4(vite@8.1.5(yaml@2.9.0))
+ typescript:
+ specifier: 6.0.3
+ version: 6.0.3
+ vite:
+ specifier: 8.1.5
+ version: 8.1.5(yaml@2.9.0)
+
+packages:
+
+ '@effect/atom-react@4.0.0-beta.102':
+ resolution: {integrity: sha512-6SX11h7OFwje4YhxYUml9qGjBU8hvnEMrLWe3TKI3lEH/W6tkU/BzajC263eXkxM68h+BJNzE81Ipi5DTfEgug==}
+ peerDependencies:
+ effect: 4.0.0-beta.102
+ react: ^19.2.4
+ scheduler: '*'
+
+ '@emnapi/core@1.11.1':
+ resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==}
+
+ '@emnapi/runtime@1.11.1':
+ resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==}
+
+ '@emnapi/wasi-threads@1.2.2':
+ resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==}
+
+ '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4':
+ resolution: {integrity: sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4':
+ resolution: {integrity: sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4':
+ resolution: {integrity: sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4':
+ resolution: {integrity: sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw==}
+ cpu: [arm]
+ os: [linux]
+
+ '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4':
+ resolution: {integrity: sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ==}
+ cpu: [x64]
+ os: [linux]
+
+ '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4':
+ resolution: {integrity: sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ==}
+ cpu: [x64]
+ os: [win32]
+
+ '@napi-rs/wasm-runtime@1.2.2':
+ resolution: {integrity: sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0}
+ peerDependencies:
+ '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.3
+ '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.3
+
+ '@oxc-project/types@0.139.0':
+ resolution: {integrity: sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==}
+
+ '@rolldown/binding-android-arm64@1.1.5':
+ resolution: {integrity: sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [android]
+
+ '@rolldown/binding-darwin-arm64@1.1.5':
+ resolution: {integrity: sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@rolldown/binding-darwin-x64@1.1.5':
+ resolution: {integrity: sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [darwin]
+
+ '@rolldown/binding-freebsd-x64@1.1.5':
+ resolution: {integrity: sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@rolldown/binding-linux-arm-gnueabihf@1.1.5':
+ resolution: {integrity: sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm]
+ os: [linux]
+
+ '@rolldown/binding-linux-arm64-gnu@1.1.5':
+ resolution: {integrity: sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rolldown/binding-linux-arm64-musl@1.1.5':
+ resolution: {integrity: sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@rolldown/binding-linux-ppc64-gnu@1.1.5':
+ resolution: {integrity: sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [ppc64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rolldown/binding-linux-s390x-gnu@1.1.5':
+ resolution: {integrity: sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [s390x]
+ os: [linux]
+ libc: [glibc]
+
+ '@rolldown/binding-linux-x64-gnu@1.1.5':
+ resolution: {integrity: sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rolldown/binding-linux-x64-musl@1.1.5':
+ resolution: {integrity: sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@rolldown/binding-openharmony-arm64@1.1.5':
+ resolution: {integrity: sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@rolldown/binding-wasm32-wasi@1.1.5':
+ resolution: {integrity: sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [wasm32]
+
+ '@rolldown/binding-win32-arm64-msvc@1.1.5':
+ resolution: {integrity: sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rolldown/binding-win32-x64-msvc@1.1.5':
+ resolution: {integrity: sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [win32]
+
+ '@rolldown/pluginutils@1.0.1':
+ resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==}
+
+ '@standard-schema/spec@1.1.0':
+ resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
+
+ '@tanstack/history@1.162.0':
+ resolution: {integrity: sha512-79pf/RkhteYZTRgcR4F9kbk84P2N8rugQJswxfIqovlbRiT3yI7eBE+5QorIrZaOKktsgzRlXh1l/du/xpl4iA==}
+ engines: {node: '>=20.19'}
+
+ '@tanstack/react-router@1.170.18':
+ resolution: {integrity: sha512-wpbGYZEp/fmz1q4bn7BD8VZ+/VZ7GBqSJv5V969pU+chP8y7dquWDmKTFMohvUegb9lg12m1uPVvD6kB2wORvQ==}
+ engines: {node: '>=20.19'}
+ peerDependencies:
+ react: '>=18.0.0 || >=19.0.0'
+ react-dom: '>=18.0.0 || >=19.0.0'
+
+ '@tanstack/react-store@0.9.3':
+ resolution: {integrity: sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ '@tanstack/router-core@1.171.15':
+ resolution: {integrity: sha512-IILCDcLaItMZQ2jEmCABHY1Nhjjn5XUvwpQp3e4Nmu+vfg0BgYFuu/QASz2SwE2ZNbVMrvt8X/wxa+Gg5aErxA==}
+ engines: {node: '>=20.19'}
+
+ '@tanstack/store@0.9.3':
+ resolution: {integrity: sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==}
+
+ '@tybys/wasm-util@0.10.3':
+ resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==}
+
+ '@typeonce/effect-machine@file:../..':
+ resolution: {directory: ../.., type: directory}
+ engines: {node: '>=20'}
+ peerDependencies:
+ effect: 4.0.0-beta.102
+
+ '@types/react-dom@19.2.2':
+ resolution: {integrity: sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==}
+ peerDependencies:
+ '@types/react': ^19.2.0
+
+ '@types/react@19.2.17':
+ resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==}
+
+ '@vitejs/plugin-react@6.0.4':
+ resolution: {integrity: sha512-XcCQz0TBpBgljhj0gMuuDj49i6Ytqh5q1osT/Gp5uAVJUCTWxyskk/l1jwYYiu2xcNHHipdMz40EGfM1VdamVg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ peerDependencies:
+ '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0
+ babel-plugin-react-compiler: ^1.0.0
+ vite: ^8.0.0
+ peerDependenciesMeta:
+ '@rolldown/plugin-babel':
+ optional: true
+ babel-plugin-react-compiler:
+ optional: true
+
+ cookie-es@3.1.1:
+ resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==}
+
+ csstype@3.2.3:
+ resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+
+ detect-libc@2.1.2:
+ resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
+ engines: {node: '>=8'}
+
+ effect@4.0.0-beta.102:
+ resolution: {integrity: sha512-z8Y+Q76Hh/kjLFZrXu8tGn6e+tDsg45R+UHhxd190pXxD53OGwf/G/zDxXTkse4HJ5mobNZfitLfUCp4fMvu6w==}
+
+ fast-check@4.9.0:
+ resolution: {integrity: sha512-7ms6T7SybUev/PQITciI0yLM2pOSFy5zpG8Ty7tQofcVaQUvrMXp6CBwqF6fThLCLOrfBtuHAtwq6Yu4XPCllg==}
+ engines: {node: '>=12.17.0'}
+
+ fdir@6.5.0:
+ resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+
+ find-my-way-ts@0.1.6:
+ resolution: {integrity: sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA==}
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ ini@7.0.0:
+ resolution: {integrity: sha512-ifK0CgjALofS5bkrcTy4RaQ9Vx2Knf/eLeIO+NaswQEpH1UblrtTSCIvN71qQDMq0PeQ/SSPojvEJp9vvvfr+w==}
+ engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0}
+
+ isbot@5.2.1:
+ resolution: {integrity: sha512-dJ+LpKyClQZ7NG+j3OensC/mAZkGpukE9YUrgPYvAZj2doVL0edfDgywTUh5CXa0o+nW9a1V9e5+CJTX8+SxRw==}
+ engines: {node: '>=18'}
+
+ kubernetes-types@1.30.0:
+ resolution: {integrity: sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q==}
+
+ lightningcss-android-arm64@1.33.0:
+ resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [android]
+
+ lightningcss-darwin-arm64@1.33.0:
+ resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ lightningcss-darwin-x64@1.33.0:
+ resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ lightningcss-freebsd-x64@1.33.0:
+ resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+
+ lightningcss-linux-arm-gnueabihf@1.33.0:
+ resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ lightningcss-linux-arm64-gnu@1.33.0:
+ resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ lightningcss-linux-arm64-musl@1.33.0:
+ resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ lightningcss-linux-x64-gnu@1.33.0:
+ resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ lightningcss-linux-x64-musl@1.33.0:
+ resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ lightningcss-win32-arm64-msvc@1.33.0:
+ resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ lightningcss-win32-x64-msvc@1.33.0:
+ resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [win32]
+
+ lightningcss@1.33.0:
+ resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==}
+ engines: {node: '>= 12.0.0'}
+
+ msgpackr-extract@3.0.4:
+ resolution: {integrity: sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw==}
+ hasBin: true
+
+ msgpackr@2.0.5:
+ resolution: {integrity: sha512-cef05H/dSYpLpqp3sj/qyZh5vhUYCalnaLO7j1yOmpsR0y/XwLVtK7r5gn+U/F7CTEfMowcGhlUQJDLcLf7jcA==}
+
+ multipasta@0.2.8:
+ resolution: {integrity: sha512-ZPWuMKyv0cSO29f7hozp+k6+crZbQijV8ipMvxNxRf2SwtYGTX1ZX89Kd20VV4H9Znonx+EQn+iy1wGQsJ+b+Q==}
+
+ nanoid@3.3.16:
+ resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ node-gyp-build-optional-packages@5.2.2:
+ resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==}
+ hasBin: true
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ picomatch@4.0.5:
+ resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==}
+ engines: {node: '>=12'}
+
+ postcss@8.5.25:
+ resolution: {integrity: sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ pure-rand@8.4.2:
+ resolution: {integrity: sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng==}
+
+ react-dom@19.2.7:
+ resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==}
+ peerDependencies:
+ react: ^19.2.7
+
+ react@19.2.7:
+ resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==}
+ engines: {node: '>=0.10.0'}
+
+ rolldown@1.1.5:
+ resolution: {integrity: sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ hasBin: true
+
+ scheduler@0.27.0:
+ resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
+
+ seroval-plugins@1.6.0:
+ resolution: {integrity: sha512-CbR5DP5DPicpd9RwRUzka7hi4x1577eGFXJVBW409LXqqJNst99JSUTZ8CXcnskQX7laPOWpmzliq0gBZEGlrQ==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ seroval: ^1.0
+
+ seroval@1.6.0:
+ resolution: {integrity: sha512-TBwwKfscTEgnBEWmYKKeCcmCGmrJi0LV6qNUY//WBA3MDesh/zfn+KOMq/ckpxM4gZ0ouAE706A1eenekM2sug==}
+ engines: {node: '>=10'}
+
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ tinyglobby@0.2.17:
+ resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
+ engines: {node: '>=12.0.0'}
+
+ toml@4.3.0:
+ resolution: {integrity: sha512-lVb8X9BsPVuH0M4BKeS91tXAmJvCjQ5UIyAbQFaxkKGyUFK2RPkhwaFSQH8vbpl1d23eu/IBH+dwVMHWaq9A5A==}
+ engines: {node: '>=20'}
+
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+ typescript@6.0.3:
+ resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ use-sync-external-store@1.6.0:
+ resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ uuid@14.0.1:
+ resolution: {integrity: sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==}
+ hasBin: true
+
+ vite@8.1.5:
+ resolution: {integrity: sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^20.19.0 || >=22.12.0
+ '@vitejs/devtools': ^0.3.0
+ esbuild: ^0.27.0 || ^0.28.0
+ jiti: '>=1.21.0'
+ less: ^4.0.0
+ sass: ^1.70.0
+ sass-embedded: ^1.70.0
+ stylus: '>=0.54.8'
+ sugarss: ^5.0.0
+ terser: ^5.16.0
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ '@vitejs/devtools':
+ optional: true
+ esbuild:
+ optional: true
+ jiti:
+ optional: true
+ less:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+
+ yaml@2.9.0:
+ resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==}
+ engines: {node: '>= 14.6'}
+ hasBin: true
+
+snapshots:
+
+ '@effect/atom-react@4.0.0-beta.102(effect@4.0.0-beta.102)(react@19.2.7)(scheduler@0.27.0)':
+ dependencies:
+ effect: 4.0.0-beta.102
+ react: 19.2.7
+ scheduler: 0.27.0
+
+ '@emnapi/core@1.11.1':
+ dependencies:
+ '@emnapi/wasi-threads': 1.2.2
+ tslib: 2.8.1
+ optional: true
+
+ '@emnapi/runtime@1.11.1':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@emnapi/wasi-threads@1.2.2':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4':
+ optional: true
+
+ '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4':
+ optional: true
+
+ '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4':
+ optional: true
+
+ '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4':
+ optional: true
+
+ '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4':
+ optional: true
+
+ '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4':
+ optional: true
+
+ '@napi-rs/wasm-runtime@1.2.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)':
+ dependencies:
+ '@emnapi/core': 1.11.1
+ '@emnapi/runtime': 1.11.1
+ '@tybys/wasm-util': 0.10.3
+ optional: true
+
+ '@oxc-project/types@0.139.0': {}
+
+ '@rolldown/binding-android-arm64@1.1.5':
+ optional: true
+
+ '@rolldown/binding-darwin-arm64@1.1.5':
+ optional: true
+
+ '@rolldown/binding-darwin-x64@1.1.5':
+ optional: true
+
+ '@rolldown/binding-freebsd-x64@1.1.5':
+ optional: true
+
+ '@rolldown/binding-linux-arm-gnueabihf@1.1.5':
+ optional: true
+
+ '@rolldown/binding-linux-arm64-gnu@1.1.5':
+ optional: true
+
+ '@rolldown/binding-linux-arm64-musl@1.1.5':
+ optional: true
+
+ '@rolldown/binding-linux-ppc64-gnu@1.1.5':
+ optional: true
+
+ '@rolldown/binding-linux-s390x-gnu@1.1.5':
+ optional: true
+
+ '@rolldown/binding-linux-x64-gnu@1.1.5':
+ optional: true
+
+ '@rolldown/binding-linux-x64-musl@1.1.5':
+ optional: true
+
+ '@rolldown/binding-openharmony-arm64@1.1.5':
+ optional: true
+
+ '@rolldown/binding-wasm32-wasi@1.1.5':
+ dependencies:
+ '@emnapi/core': 1.11.1
+ '@emnapi/runtime': 1.11.1
+ '@napi-rs/wasm-runtime': 1.2.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)
+ optional: true
+
+ '@rolldown/binding-win32-arm64-msvc@1.1.5':
+ optional: true
+
+ '@rolldown/binding-win32-x64-msvc@1.1.5':
+ optional: true
+
+ '@rolldown/pluginutils@1.0.1': {}
+
+ '@standard-schema/spec@1.1.0': {}
+
+ '@tanstack/history@1.162.0': {}
+
+ '@tanstack/react-router@1.170.18(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
+ dependencies:
+ '@tanstack/history': 1.162.0
+ '@tanstack/react-store': 0.9.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@tanstack/router-core': 1.171.15
+ isbot: 5.2.1
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+
+ '@tanstack/react-store@0.9.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
+ dependencies:
+ '@tanstack/store': 0.9.3
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+ use-sync-external-store: 1.6.0(react@19.2.7)
+
+ '@tanstack/router-core@1.171.15':
+ dependencies:
+ '@tanstack/history': 1.162.0
+ cookie-es: 3.1.1
+ seroval: 1.6.0
+ seroval-plugins: 1.6.0(seroval@1.6.0)
+
+ '@tanstack/store@0.9.3': {}
+
+ '@tybys/wasm-util@0.10.3':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@typeonce/effect-machine@file:../..(effect@4.0.0-beta.102)':
+ dependencies:
+ effect: 4.0.0-beta.102
+
+ '@types/react-dom@19.2.2(@types/react@19.2.17)':
+ dependencies:
+ '@types/react': 19.2.17
+
+ '@types/react@19.2.17':
+ dependencies:
+ csstype: 3.2.3
+
+ '@vitejs/plugin-react@6.0.4(vite@8.1.5(yaml@2.9.0))':
+ dependencies:
+ '@rolldown/pluginutils': 1.0.1
+ vite: 8.1.5(yaml@2.9.0)
+
+ cookie-es@3.1.1: {}
+
+ csstype@3.2.3: {}
+
+ detect-libc@2.1.2: {}
+
+ effect@4.0.0-beta.102:
+ dependencies:
+ '@standard-schema/spec': 1.1.0
+ fast-check: 4.9.0
+ find-my-way-ts: 0.1.6
+ ini: 7.0.0
+ kubernetes-types: 1.30.0
+ msgpackr: 2.0.5
+ multipasta: 0.2.8
+ toml: 4.3.0
+ uuid: 14.0.1
+ yaml: 2.9.0
+
+ fast-check@4.9.0:
+ dependencies:
+ pure-rand: 8.4.2
+
+ fdir@6.5.0(picomatch@4.0.5):
+ optionalDependencies:
+ picomatch: 4.0.5
+
+ find-my-way-ts@0.1.6: {}
+
+ fsevents@2.3.3:
+ optional: true
+
+ ini@7.0.0: {}
+
+ isbot@5.2.1: {}
+
+ kubernetes-types@1.30.0: {}
+
+ lightningcss-android-arm64@1.33.0:
+ optional: true
+
+ lightningcss-darwin-arm64@1.33.0:
+ optional: true
+
+ lightningcss-darwin-x64@1.33.0:
+ optional: true
+
+ lightningcss-freebsd-x64@1.33.0:
+ optional: true
+
+ lightningcss-linux-arm-gnueabihf@1.33.0:
+ optional: true
+
+ lightningcss-linux-arm64-gnu@1.33.0:
+ optional: true
+
+ lightningcss-linux-arm64-musl@1.33.0:
+ optional: true
+
+ lightningcss-linux-x64-gnu@1.33.0:
+ optional: true
+
+ lightningcss-linux-x64-musl@1.33.0:
+ optional: true
+
+ lightningcss-win32-arm64-msvc@1.33.0:
+ optional: true
+
+ lightningcss-win32-x64-msvc@1.33.0:
+ optional: true
+
+ lightningcss@1.33.0:
+ dependencies:
+ detect-libc: 2.1.2
+ optionalDependencies:
+ lightningcss-android-arm64: 1.33.0
+ lightningcss-darwin-arm64: 1.33.0
+ lightningcss-darwin-x64: 1.33.0
+ lightningcss-freebsd-x64: 1.33.0
+ lightningcss-linux-arm-gnueabihf: 1.33.0
+ lightningcss-linux-arm64-gnu: 1.33.0
+ lightningcss-linux-arm64-musl: 1.33.0
+ lightningcss-linux-x64-gnu: 1.33.0
+ lightningcss-linux-x64-musl: 1.33.0
+ lightningcss-win32-arm64-msvc: 1.33.0
+ lightningcss-win32-x64-msvc: 1.33.0
+
+ msgpackr-extract@3.0.4:
+ dependencies:
+ node-gyp-build-optional-packages: 5.2.2
+ optionalDependencies:
+ '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.4
+ '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.4
+ '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.4
+ '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.4
+ '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.4
+ '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.4
+ optional: true
+
+ msgpackr@2.0.5:
+ optionalDependencies:
+ msgpackr-extract: 3.0.4
+
+ multipasta@0.2.8: {}
+
+ nanoid@3.3.16: {}
+
+ node-gyp-build-optional-packages@5.2.2:
+ dependencies:
+ detect-libc: 2.1.2
+ optional: true
+
+ picocolors@1.1.1: {}
+
+ picomatch@4.0.5: {}
+
+ postcss@8.5.25:
+ dependencies:
+ nanoid: 3.3.16
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ pure-rand@8.4.2: {}
+
+ react-dom@19.2.7(react@19.2.7):
+ dependencies:
+ react: 19.2.7
+ scheduler: 0.27.0
+
+ react@19.2.7: {}
+
+ rolldown@1.1.5:
+ dependencies:
+ '@oxc-project/types': 0.139.0
+ '@rolldown/pluginutils': 1.0.1
+ optionalDependencies:
+ '@rolldown/binding-android-arm64': 1.1.5
+ '@rolldown/binding-darwin-arm64': 1.1.5
+ '@rolldown/binding-darwin-x64': 1.1.5
+ '@rolldown/binding-freebsd-x64': 1.1.5
+ '@rolldown/binding-linux-arm-gnueabihf': 1.1.5
+ '@rolldown/binding-linux-arm64-gnu': 1.1.5
+ '@rolldown/binding-linux-arm64-musl': 1.1.5
+ '@rolldown/binding-linux-ppc64-gnu': 1.1.5
+ '@rolldown/binding-linux-s390x-gnu': 1.1.5
+ '@rolldown/binding-linux-x64-gnu': 1.1.5
+ '@rolldown/binding-linux-x64-musl': 1.1.5
+ '@rolldown/binding-openharmony-arm64': 1.1.5
+ '@rolldown/binding-wasm32-wasi': 1.1.5
+ '@rolldown/binding-win32-arm64-msvc': 1.1.5
+ '@rolldown/binding-win32-x64-msvc': 1.1.5
+
+ scheduler@0.27.0: {}
+
+ seroval-plugins@1.6.0(seroval@1.6.0):
+ dependencies:
+ seroval: 1.6.0
+
+ seroval@1.6.0: {}
+
+ source-map-js@1.2.1: {}
+
+ tinyglobby@0.2.17:
+ dependencies:
+ fdir: 6.5.0(picomatch@4.0.5)
+ picomatch: 4.0.5
+
+ toml@4.3.0: {}
+
+ tslib@2.8.1:
+ optional: true
+
+ typescript@6.0.3: {}
+
+ use-sync-external-store@1.6.0(react@19.2.7):
+ dependencies:
+ react: 19.2.7
+
+ uuid@14.0.1: {}
+
+ vite@8.1.5(yaml@2.9.0):
+ dependencies:
+ lightningcss: 1.33.0
+ picomatch: 4.0.5
+ postcss: 8.5.25
+ rolldown: 1.1.5
+ tinyglobby: 0.2.17
+ optionalDependencies:
+ fsevents: 2.3.3
+ yaml: 2.9.0
+
+ yaml@2.9.0: {}
diff --git a/examples/playground/pnpm-workspace.yaml b/examples/playground/pnpm-workspace.yaml
new file mode 100644
index 0000000..d80d736
--- /dev/null
+++ b/examples/playground/pnpm-workspace.yaml
@@ -0,0 +1,10 @@
+packages:
+ - "."
+
+overrides:
+ effect: 4.0.0-beta.102
+
+minimumReleaseAge: 1440
+minimumReleaseAgeExclude:
+ - effect@4.0.0-beta.102
+
diff --git a/examples/playground/src/components/ExamplePage.tsx b/examples/playground/src/components/ExamplePage.tsx
new file mode 100644
index 0000000..7e03044
--- /dev/null
+++ b/examples/playground/src/components/ExamplePage.tsx
@@ -0,0 +1,34 @@
+import type { ReactNode } from "react"
+
+export function ExamplePage({
+ title,
+ summary,
+ machineFile,
+ children
+}: {
+ readonly title: string
+ readonly summary: string
+ readonly machineFile: string
+ readonly children: ReactNode
+}) {
+ return (
+
+
+ Machine starter
+ {title}
+ {summary}
+ {machineFile}
+
+ {children}
+
+ )
+}
+
+export function StarterPanel({ children }: { readonly children: ReactNode }) {
+ return (
+
+
Implementation area
+ {children}
+
+ )
+}
diff --git a/examples/playground/src/examples/media-player/MediaPlayerPage.tsx b/examples/playground/src/examples/media-player/MediaPlayerPage.tsx
new file mode 100644
index 0000000..79a09bf
--- /dev/null
+++ b/examples/playground/src/examples/media-player/MediaPlayerPage.tsx
@@ -0,0 +1,49 @@
+import { useEffect, useState } from "react"
+import { ExamplePage, StarterPanel } from "../../components/ExamplePage.tsx"
+import { MediaPlayerMachine } from "./machine.ts"
+
+export function MediaPlayerPage() {
+ void MediaPlayerMachine
+
+ const [source, setSource] = useState()
+
+ useEffect(
+ () => () => {
+ if (source !== undefined) URL.revokeObjectURL(source)
+ },
+ [source]
+ )
+
+ return (
+
+
+
+ Choose an audio file
+ {
+ const file = event.currentTarget.files?.[0]
+ if (file === undefined) return
+ if (source !== undefined) URL.revokeObjectURL(source)
+ setSource(URL.createObjectURL(file))
+ }}
+ />
+
+
+ Your browser does not support audio playback.
+
+ Translate DOM events
+
+ The statechart separates playback and volume. Wire playing, pause,{" "}
+ waiting,{` `}
+ canplay, ended, and error into the prepared events.
+
+
+
+ )
+}
diff --git a/examples/playground/src/examples/media-player/machine.ts b/examples/playground/src/examples/media-player/machine.ts
new file mode 100644
index 0000000..d4d60b1
--- /dev/null
+++ b/examples/playground/src/examples/media-player/machine.ts
@@ -0,0 +1,107 @@
+import { Machine } from "@typeonce/effect-machine"
+import { Schema } from "effect"
+
+export const MediaPlayerState = Schema.TaggedUnion({
+ Player: {},
+ playback: {},
+ Empty: {},
+ Loading: {},
+ Paused: {},
+ Playing: {},
+ Buffering: {},
+ Ended: {},
+ Failed: { message: Schema.String },
+ volume: {},
+ Audible: { volume: Schema.Number },
+ Muted: { previousVolume: Schema.Number }
+})
+
+export const MediaPlayerEvent = Schema.TaggedUnion({
+ SourceSelected: { url: Schema.String },
+ PlayRequested: {},
+ PauseRequested: {},
+ MediaPlaying: {},
+ MediaPaused: {},
+ MediaWaiting: {},
+ MediaCanPlay: {},
+ MediaEnded: {},
+ MediaFailed: { message: Schema.String },
+ VolumeChanged: { volume: Schema.Number },
+ MuteToggled: {}
+})
+
+export const MediaPlayerStates = Machine.defineStates({
+ Player: {
+ schema: MediaPlayerState.cases.Player,
+ type: "parallel",
+ states: {
+ playback: {
+ schema: MediaPlayerState.cases.playback,
+ initial: "Empty",
+ states: {
+ Empty: MediaPlayerState.cases.Empty,
+ Loading: MediaPlayerState.cases.Loading,
+ Paused: MediaPlayerState.cases.Paused,
+ Playing: MediaPlayerState.cases.Playing,
+ Buffering: MediaPlayerState.cases.Buffering,
+ Ended: MediaPlayerState.cases.Ended,
+ Failed: MediaPlayerState.cases.Failed
+ }
+ },
+ volume: {
+ schema: MediaPlayerState.cases.volume,
+ initial: "Audible",
+ states: {
+ Audible: MediaPlayerState.cases.Audible,
+ Muted: MediaPlayerState.cases.Muted
+ }
+ }
+ }
+ }
+})
+
+const initialPlayer = () =>
+ MediaPlayerStates.initial.Player(MediaPlayerState.cases.Player.make({}), (player) =>
+ player
+ .playback(
+ MediaPlayerState.cases.playback.make({}),
+ (playback) => playback.Empty(MediaPlayerState.cases.Empty.make({}))
+ )
+ .volume(
+ MediaPlayerState.cases.volume.make({}),
+ (volume) => volume.Audible(MediaPlayerState.cases.Audible.make({ volume: 1 }))
+ ))
+
+/**
+ * The browser event protocol and parallel topology are ready. Keep calls to
+ * HTMLAudioElement.play/pause in the React adapter (or staged actions), and
+ * feed resulting DOM events back into this machine.
+ */
+export const MediaPlayerMachine = Machine.make({
+ id: "MediaPlayer",
+ states: MediaPlayerStates.states,
+ events: [MediaPlayerEvent],
+ initial: initialPlayer
+}).handle({
+ Player: {
+ states: {
+ playback: {
+ states: {
+ Empty: {},
+ Loading: {},
+ Paused: {},
+ Playing: {},
+ Buffering: {},
+ Ended: {},
+ Failed: {}
+ }
+ },
+ volume: {
+ states: {
+ Audible: {},
+ Muted: {}
+ }
+ }
+ }
+ }
+})
diff --git a/examples/playground/src/examples/microwave/MicrowavePage.tsx b/examples/playground/src/examples/microwave/MicrowavePage.tsx
new file mode 100644
index 0000000..906cac5
--- /dev/null
+++ b/examples/playground/src/examples/microwave/MicrowavePage.tsx
@@ -0,0 +1,30 @@
+import { ExamplePage, StarterPanel } from "../../components/ExamplePage.tsx"
+import { MicrowaveMachine } from "./machine.ts"
+
+export function MicrowavePage() {
+ void MicrowaveMachine
+
+ return (
+
+
+
+ Define the safety rules
+
+ The parallel statechart is scaffolded. Add transitions for power, door changes, elapsed time, and automatic
+ interruption.
+
+
+
+ )
+}
diff --git a/examples/playground/src/examples/microwave/machine.ts b/examples/playground/src/examples/microwave/machine.ts
new file mode 100644
index 0000000..58e98d7
--- /dev/null
+++ b/examples/playground/src/examples/microwave/machine.ts
@@ -0,0 +1,81 @@
+import { Machine } from "@typeonce/effect-machine"
+import { Schema } from "effect"
+
+export const MicrowaveState = Schema.TaggedUnion({
+ Oven: { elapsedSeconds: Schema.Number },
+ Engine: {},
+ Off: {},
+ Idle: {},
+ Cooking: {},
+ Door: {},
+ Closed: {},
+ Open: {}
+})
+
+export const MicrowaveEvent = Schema.TaggedUnion({
+ PowerPressed: {},
+ DoorOpened: {},
+ DoorClosed: {},
+ SecondElapsed: {}
+})
+
+export const MicrowaveStates = Machine.defineStates({
+ Oven: {
+ schema: MicrowaveState.cases.Oven,
+ type: "parallel",
+ states: {
+ engine: {
+ schema: MicrowaveState.cases.Engine,
+ initial: "Off",
+ states: {
+ Off: MicrowaveState.cases.Off,
+ Idle: MicrowaveState.cases.Idle,
+ Cooking: MicrowaveState.cases.Cooking
+ }
+ },
+ door: {
+ schema: MicrowaveState.cases.Door,
+ initial: "Closed",
+ states: {
+ Closed: MicrowaveState.cases.Closed,
+ Open: MicrowaveState.cases.Open
+ }
+ }
+ }
+ }
+})
+
+const initialMicrowave = () =>
+ MicrowaveStates.initial.Oven(MicrowaveState.cases.Oven.make({ elapsedSeconds: 0 }), (oven) =>
+ oven
+ .engine(MicrowaveState.cases.Engine.make({}), (engine) => engine.Off(MicrowaveState.cases.Off.make({})))
+ .door(MicrowaveState.cases.Door.make({}), (door) => door.Closed(MicrowaveState.cases.Closed.make({}))))
+
+/**
+ * The parallel topology is ready; fill the handlers with the safety rules you
+ * want to explore (for example, opening the door must interrupt cooking).
+ */
+export const MicrowaveMachine = Machine.make({
+ id: "Microwave",
+ states: MicrowaveStates.states,
+ events: [MicrowaveEvent],
+ initial: initialMicrowave
+}).handle({
+ Oven: {
+ states: {
+ engine: {
+ states: {
+ Off: {},
+ Idle: {},
+ Cooking: {}
+ }
+ },
+ door: {
+ states: {
+ Closed: {},
+ Open: {}
+ }
+ }
+ }
+ }
+})
diff --git a/examples/playground/src/examples/traffic-light/TrafficLightPage.tsx b/examples/playground/src/examples/traffic-light/TrafficLightPage.tsx
new file mode 100644
index 0000000..fc7fa2b
--- /dev/null
+++ b/examples/playground/src/examples/traffic-light/TrafficLightPage.tsx
@@ -0,0 +1,27 @@
+import { ExamplePage, StarterPanel } from "../../components/ExamplePage.tsx"
+import { TrafficLightMachine } from "./machine.ts"
+
+export function TrafficLightPage() {
+ void TrafficLightMachine
+
+ return (
+
+
+
+
+
+
+
+ Add state-scoped timers
+
+ The transition cycle is ready. Replace manual events with{" "}
+ Machine.after, then render the current state here.
+
+
+
+ )
+}
diff --git a/examples/playground/src/examples/traffic-light/machine.ts b/examples/playground/src/examples/traffic-light/machine.ts
new file mode 100644
index 0000000..cf94154
--- /dev/null
+++ b/examples/playground/src/examples/traffic-light/machine.ts
@@ -0,0 +1,43 @@
+import { Machine } from "@typeonce/effect-machine"
+import { Schema } from "effect"
+
+export const TrafficLightState = Schema.TaggedUnion({
+ Red: {},
+ RedYellow: {},
+ Green: {},
+ Yellow: {}
+})
+
+export const TrafficLightEvent = Schema.TaggedUnion({
+ TimerElapsed: {}
+})
+
+export const TrafficLightStates = Machine.defineStates(TrafficLightState.cases)
+
+export const TrafficLightMachine = Machine.make({
+ id: "TrafficLight",
+ states: TrafficLightStates.states,
+ events: [TrafficLightEvent],
+ initial: () => TrafficLightStates.initial.Red(TrafficLightState.cases.Red.make({}))
+}).handle({
+ Red: {
+ on: {
+ TimerElapsed: ({ target }) => target.full.RedYellow(TrafficLightState.cases.RedYellow.make({}))
+ }
+ },
+ RedYellow: {
+ on: {
+ TimerElapsed: ({ target }) => target.full.Green(TrafficLightState.cases.Green.make({}))
+ }
+ },
+ Green: {
+ on: {
+ TimerElapsed: ({ target }) => target.full.Yellow(TrafficLightState.cases.Yellow.make({}))
+ }
+ },
+ Yellow: {
+ on: {
+ TimerElapsed: ({ target }) => target.full.Red(TrafficLightState.cases.Red.make({}))
+ }
+ }
+})
diff --git a/examples/playground/src/examples/turnstile/TurnstilePage.tsx b/examples/playground/src/examples/turnstile/TurnstilePage.tsx
new file mode 100644
index 0000000..3846ef0
--- /dev/null
+++ b/examples/playground/src/examples/turnstile/TurnstilePage.tsx
@@ -0,0 +1,26 @@
+import { ExamplePage, StarterPanel } from "../../components/ExamplePage.tsx"
+import { TurnstileMachine } from "./machine.ts"
+
+export function TurnstilePage() {
+ void TurnstileMachine
+
+ return (
+
+
+
+ Connect the machine
+
+ The starter already models Locked, Unlocked, CoinInserted, and{` `}
+ GatePushed. Mount it with AtomMachine and add the controls here.
+
+
+
+ )
+}
diff --git a/examples/playground/src/examples/turnstile/machine.ts b/examples/playground/src/examples/turnstile/machine.ts
new file mode 100644
index 0000000..0d439a5
--- /dev/null
+++ b/examples/playground/src/examples/turnstile/machine.ts
@@ -0,0 +1,32 @@
+import { Machine } from "@typeonce/effect-machine"
+import { Schema } from "effect"
+
+export const TurnstileState = Schema.TaggedUnion({
+ Locked: {},
+ Unlocked: {}
+})
+
+export const TurnstileEvent = Schema.TaggedUnion({
+ CoinInserted: {},
+ GatePushed: {}
+})
+
+export const TurnstileStates = Machine.defineStates(TurnstileState.cases)
+
+export const TurnstileMachine = Machine.make({
+ id: "Turnstile",
+ states: TurnstileStates.states,
+ events: [TurnstileEvent],
+ initial: () => TurnstileStates.initial.Locked.from()
+}).handle({
+ Locked: {
+ on: {
+ CoinInserted: ({ target }) => target.full.Unlocked.from()
+ }
+ },
+ Unlocked: {
+ on: {
+ GatePushed: ({ target }) => target.full.Locked.from()
+ }
+ }
+})
diff --git a/examples/playground/src/examples/worker-tabs/WorkerTabsPage.tsx b/examples/playground/src/examples/worker-tabs/WorkerTabsPage.tsx
new file mode 100644
index 0000000..3fba1fb
--- /dev/null
+++ b/examples/playground/src/examples/worker-tabs/WorkerTabsPage.tsx
@@ -0,0 +1,91 @@
+import { useEffect, useRef, useState } from "react"
+import { ExamplePage, StarterPanel } from "../../components/ExamplePage.tsx"
+import { SharedMachine } from "./machine.ts"
+import type { WorkerResponse } from "./protocol.ts"
+import { createTabChannel } from "./tab-channel.ts"
+import { createMachineWorker, type MachineWorkerClient } from "./worker-client.ts"
+
+export function WorkerTabsPage() {
+ void SharedMachine
+
+ const workerRef = useRef(null)
+ const channelRef = useRef | null>(null)
+ const tabId = useRef(crypto.randomUUID().slice(0, 8))
+ const [workerStatus, setWorkerStatus] = useState("starting")
+ const [tabMessage, setTabMessage] = useState("Open this route in another tab to test the channel.")
+
+ useEffect(() => {
+ const worker = createMachineWorker()
+ const channel = createTabChannel()
+ workerRef.current = worker
+ channelRef.current = channel
+
+ const unsubscribeWorker = worker.subscribe((response: WorkerResponse) => {
+ switch (response._tag) {
+ case "Ready":
+ setWorkerStatus("ready")
+ break
+ case "Pong":
+ setWorkerStatus(`pong ${response.requestId.slice(0, 8)}`)
+ break
+ case "MachineSnapshot":
+ setWorkerStatus("snapshot received")
+ break
+ case "WorkerError":
+ setWorkerStatus(response.message)
+ }
+ })
+
+ const unsubscribeTabs = channel.subscribe((message) => {
+ setTabMessage(`Message from tab ${message.senderId} at ${new Date(message.sentAt).toLocaleTimeString()}`)
+ })
+
+ return () => {
+ unsubscribeWorker()
+ unsubscribeTabs()
+ worker.close()
+ channel.close()
+ workerRef.current = null
+ channelRef.current = null
+ }
+ }, [])
+
+ return (
+
+
+
+
+
+ Web Worker
+ {workerStatus}
+ workerRef.current?.send({ _tag: "Ping", requestId: crypto.randomUUID() })}
+ >
+ Ping worker
+
+
+
+
+ Between tabs
+ {tabMessage}
+ channelRef.current?.announce(tabId.current)}
+ >
+ Announce this tab
+
+
+
+
+ Start SharedMachine in machine.worker.ts, forward public events through{` `}
+ MachineEvent, and publish runtime snapshots through MachineSnapshot.
+
+
+
+ )
+}
diff --git a/examples/playground/src/examples/worker-tabs/machine.ts b/examples/playground/src/examples/worker-tabs/machine.ts
new file mode 100644
index 0000000..e156d5e
--- /dev/null
+++ b/examples/playground/src/examples/worker-tabs/machine.ts
@@ -0,0 +1,39 @@
+import { Machine } from "@typeonce/effect-machine"
+import { Schema } from "effect"
+
+export const SharedMachineState = Schema.TaggedUnion({
+ Idle: { count: Schema.Number },
+ Active: { count: Schema.Number }
+})
+
+export const SharedMachineEvent = Schema.TaggedUnion({
+ Started: {},
+ Incremented: {},
+ Reset: {},
+ Stopped: {}
+})
+
+export const SharedMachineStates = Machine.defineStates(SharedMachineState.cases)
+
+/** Replace or extend this machine, then start it from machine.worker.ts. */
+export const SharedMachine = Machine.make({
+ id: "WorkerHostedMachine",
+ states: SharedMachineStates.states,
+ events: [SharedMachineEvent],
+ initial: () => SharedMachineStates.initial.Idle(SharedMachineState.cases.Idle.make({ count: 0 }))
+}).handle({
+ Idle: {
+ on: {
+ Started: ({ state, target }) => target.full.Active(SharedMachineState.cases.Active.make({ count: state.count })),
+ Reset: ({ target }) => target.full.Idle(SharedMachineState.cases.Idle.make({ count: 0 }))
+ }
+ },
+ Active: {
+ on: {
+ Incremented: ({ state, target }) =>
+ target.full.Active(SharedMachineState.cases.Active.make({ count: state.count + 1 })),
+ Reset: ({ target }) => target.full.Active(SharedMachineState.cases.Active.make({ count: 0 })),
+ Stopped: ({ state, target }) => target.full.Idle(SharedMachineState.cases.Idle.make({ count: state.count }))
+ }
+ }
+})
diff --git a/examples/playground/src/examples/worker-tabs/machine.worker.ts b/examples/playground/src/examples/worker-tabs/machine.worker.ts
new file mode 100644
index 0000000..4ff720a
--- /dev/null
+++ b/examples/playground/src/examples/worker-tabs/machine.worker.ts
@@ -0,0 +1,26 @@
+///
+
+import type { WorkerRequest, WorkerResponse } from "./protocol.ts"
+
+const workerScope: DedicatedWorkerGlobalScope = self as DedicatedWorkerGlobalScope
+
+const post = (response: WorkerResponse) => workerScope.postMessage(response)
+
+post({ _tag: "Ready" })
+
+workerScope.addEventListener("message", (message: MessageEvent) => {
+ switch (message.data._tag) {
+ case "Ping":
+ post({ _tag: "Pong", requestId: message.data.requestId })
+ return
+ case "MachineEvent":
+ // Plug-in point: start SharedMachine once with Machine.start, send the
+ // decoded event to its ref, and publish snapshots from Machine.watch.
+ post({
+ _tag: "WorkerError",
+ message: "Machine transport is ready; connect SharedMachine in machine.worker.ts."
+ })
+ }
+})
+
+export {}
diff --git a/examples/playground/src/examples/worker-tabs/protocol.ts b/examples/playground/src/examples/worker-tabs/protocol.ts
new file mode 100644
index 0000000..b9fa78d
--- /dev/null
+++ b/examples/playground/src/examples/worker-tabs/protocol.ts
@@ -0,0 +1,15 @@
+export type WorkerRequest =
+ | { readonly _tag: "Ping"; readonly requestId: string }
+ | { readonly _tag: "MachineEvent"; readonly event: unknown }
+
+export type WorkerResponse =
+ | { readonly _tag: "Ready" }
+ | { readonly _tag: "Pong"; readonly requestId: string }
+ | { readonly _tag: "MachineSnapshot"; readonly snapshot: unknown }
+ | { readonly _tag: "WorkerError"; readonly message: string }
+
+export type TabMessage = {
+ readonly _tag: "TabAnnouncement"
+ readonly senderId: string
+ readonly sentAt: number
+}
diff --git a/examples/playground/src/examples/worker-tabs/tab-channel.ts b/examples/playground/src/examples/worker-tabs/tab-channel.ts
new file mode 100644
index 0000000..0e5b0ac
--- /dev/null
+++ b/examples/playground/src/examples/worker-tabs/tab-channel.ts
@@ -0,0 +1,18 @@
+import type { TabMessage } from "./protocol.ts"
+
+const channelName = "effect-machine-example-tabs"
+
+export function createTabChannel() {
+ const channel = new BroadcastChannel(channelName)
+
+ return {
+ announce: (senderId: string) =>
+ channel.postMessage({ _tag: "TabAnnouncement", senderId, sentAt: Date.now() } satisfies TabMessage),
+ subscribe: (listener: (message: TabMessage) => void) => {
+ const onMessage = (event: MessageEvent) => listener(event.data)
+ channel.addEventListener("message", onMessage)
+ return () => channel.removeEventListener("message", onMessage)
+ },
+ close: () => channel.close()
+ }
+}
diff --git a/examples/playground/src/examples/worker-tabs/worker-client.ts b/examples/playground/src/examples/worker-tabs/worker-client.ts
new file mode 100644
index 0000000..85bd03f
--- /dev/null
+++ b/examples/playground/src/examples/worker-tabs/worker-client.ts
@@ -0,0 +1,21 @@
+import type { WorkerRequest, WorkerResponse } from "./protocol.ts"
+
+export function createMachineWorker() {
+ const worker = new Worker(new URL("./machine.worker.ts", import.meta.url), {
+ type: "module",
+ name: "effect-machine-example"
+ })
+
+ return {
+ worker,
+ send: (request: WorkerRequest) => worker.postMessage(request),
+ subscribe: (listener: (response: WorkerResponse) => void) => {
+ const onMessage = (event: MessageEvent) => listener(event.data)
+ worker.addEventListener("message", onMessage)
+ return () => worker.removeEventListener("message", onMessage)
+ },
+ close: () => worker.terminate()
+ }
+}
+
+export type MachineWorkerClient = ReturnType
diff --git a/examples/playground/src/main.tsx b/examples/playground/src/main.tsx
new file mode 100644
index 0000000..ab5c64b
--- /dev/null
+++ b/examples/playground/src/main.tsx
@@ -0,0 +1,20 @@
+import { RegistryProvider } from "@effect/atom-react"
+import { RouterProvider } from "@tanstack/react-router"
+import { StrictMode } from "react"
+import { createRoot } from "react-dom/client"
+import { router } from "./router.tsx"
+import "./styles.css"
+
+const root = document.getElementById("root")
+
+if (root === null) {
+ throw new Error("Root element not found")
+}
+
+createRoot(root).render(
+
+
+
+
+
+)
diff --git a/examples/playground/src/router.tsx b/examples/playground/src/router.tsx
new file mode 100644
index 0000000..c34dea6
--- /dev/null
+++ b/examples/playground/src/router.tsx
@@ -0,0 +1,134 @@
+import { createRootRoute, createRoute, createRouter, Link, Outlet } from "@tanstack/react-router"
+import { MediaPlayerPage } from "./examples/media-player/MediaPlayerPage.tsx"
+import { MicrowavePage } from "./examples/microwave/MicrowavePage.tsx"
+import { TrafficLightPage } from "./examples/traffic-light/TrafficLightPage.tsx"
+import { TurnstilePage } from "./examples/turnstile/TurnstilePage.tsx"
+import { WorkerTabsPage } from "./examples/worker-tabs/WorkerTabsPage.tsx"
+
+const examples = [
+ {
+ to: "/turnstile" as const,
+ title: "Turnstile",
+ description: "A compact baseline for states, events, and legal transitions.",
+ concepts: ["atomic states", "typed events"]
+ },
+ {
+ to: "/traffic-light" as const,
+ title: "Traffic light",
+ description: "A timed cycle with state-scoped delays and repeated transitions.",
+ concepts: ["timers", "entry and exit"]
+ },
+ {
+ to: "/microwave" as const,
+ title: "Microwave",
+ description: "Door and engine behavior modeled as cooperating parallel regions.",
+ concepts: ["parallel states", "conditions"]
+ },
+ {
+ to: "/media-player" as const,
+ title: "Media player",
+ description: "Connect a real audio element lifecycle to a typed statechart.",
+ concepts: ["DOM events", "parallel concerns"]
+ },
+ {
+ to: "/worker-tabs" as const,
+ title: "Workers and tabs",
+ description: "Host a machine off the main thread and synchronize browser tabs.",
+ concepts: ["Web Worker", "BroadcastChannel"]
+ }
+]
+
+const rootRoute = createRootRoute({
+ component: RootLayout
+})
+
+function RootLayout() {
+ return (
+
+
+
+
+ Effect Machine
+
+
+ {examples.map((example) => (
+
+ {example.title}
+
+ ))}
+
+
+
+
+
+
+ )
+}
+
+const homeRoute = createRoute({
+ getParentRoute: () => rootRoute,
+ path: "/",
+ component: HomePage
+})
+
+function HomePage() {
+ return (
+
+
+
+ {examples.map((example, index) => (
+
+
{String(index + 1).padStart(2, "0")}
+
{example.title}
+
{example.description}
+
+ {example.concepts.map((concept) => {concept} )}
+
+
Open starter →
+
+ ))}
+
+
+ )
+}
+
+const turnstileRoute = createRoute({ getParentRoute: () => rootRoute, path: "/turnstile", component: TurnstilePage })
+const trafficLightRoute = createRoute({
+ getParentRoute: () => rootRoute,
+ path: "/traffic-light",
+ component: TrafficLightPage
+})
+const microwaveRoute = createRoute({ getParentRoute: () => rootRoute, path: "/microwave", component: MicrowavePage })
+const mediaPlayerRoute = createRoute({
+ getParentRoute: () => rootRoute,
+ path: "/media-player",
+ component: MediaPlayerPage
+})
+const workerTabsRoute = createRoute({
+ getParentRoute: () => rootRoute,
+ path: "/worker-tabs",
+ component: WorkerTabsPage
+})
+
+const routeTree = rootRoute.addChildren([
+ homeRoute,
+ turnstileRoute,
+ trafficLightRoute,
+ microwaveRoute,
+ mediaPlayerRoute,
+ workerTabsRoute
+])
+
+export const router = createRouter({ routeTree })
+
+declare module "@tanstack/react-router" {
+ interface Register {
+ router: typeof router
+ }
+}
diff --git a/examples/playground/src/styles.css b/examples/playground/src/styles.css
new file mode 100644
index 0000000..67145f9
--- /dev/null
+++ b/examples/playground/src/styles.css
@@ -0,0 +1,430 @@
+:root {
+ color: #e8ecf4;
+ background: #0c0f15;
+ font-family:
+ Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+ font-synthesis: none;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+ min-width: 20rem;
+ min-height: 100vh;
+ background:
+ radial-gradient(circle at 8% 0%, rgb(93 117 255 / 18%), transparent 28rem),
+ radial-gradient(circle at 95% 18%, rgb(56 214 162 / 10%), transparent 22rem), #0c0f15;
+}
+
+button,
+input {
+ font: inherit;
+}
+
+button {
+ padding: 0.65rem 0.9rem;
+ border: 1px solid #354057;
+ border-radius: 0.6rem;
+ color: #f4f6fa;
+ background: #20283a;
+ font-weight: 700;
+ cursor: pointer;
+}
+
+button:hover {
+ border-color: #7085ff;
+ background: #2a3550;
+}
+
+code {
+ font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
+}
+
+.app-shell {
+ width: min(76rem, calc(100% - 2rem));
+ margin: 0 auto;
+}
+
+.site-header {
+ display: flex;
+ gap: 2rem;
+ align-items: center;
+ justify-content: space-between;
+ min-height: 4.75rem;
+ border-bottom: 1px solid #252b38;
+}
+
+.brand {
+ display: inline-flex;
+ gap: 0.65rem;
+ align-items: center;
+ color: #f7f8fb;
+ font-weight: 800;
+ text-decoration: none;
+ white-space: nowrap;
+}
+
+.brand-mark {
+ width: 0.85rem;
+ height: 0.85rem;
+ border: 2px solid #9aaaFF;
+ border-radius: 0.2rem;
+ box-shadow: 0.28rem 0.28rem 0 #42d6a3;
+ transform: rotate(45deg);
+}
+
+nav {
+ display: flex;
+ gap: 0.3rem;
+ overflow-x: auto;
+ padding: 0.5rem 0;
+}
+
+nav a {
+ padding: 0.5rem 0.7rem;
+ border-radius: 0.5rem;
+ color: #8f98aa;
+ font-size: 0.82rem;
+ font-weight: 650;
+ text-decoration: none;
+ white-space: nowrap;
+}
+
+nav a:hover,
+nav a.is-active {
+ color: #eef1ff;
+ background: #202638;
+}
+
+main {
+ padding: clamp(3rem, 8vw, 6.5rem) 0 6rem;
+}
+
+h1,
+h2,
+p {
+ margin-top: 0;
+}
+
+h1 {
+ max-width: 13ch;
+ margin-bottom: 1.25rem;
+ font-size: clamp(3rem, 8vw, 6.6rem);
+ line-height: 0.92;
+ letter-spacing: -0.07em;
+}
+
+.eyebrow,
+.starter-label {
+ margin-bottom: 1rem;
+ color: #7e91ff;
+ font-family: "SFMono-Regular", Consolas, monospace;
+ font-size: 0.72rem;
+ font-weight: 800;
+ letter-spacing: 0.14em;
+ text-transform: uppercase;
+}
+
+.lede {
+ max-width: 42rem;
+ color: #a8b0c0;
+ font-size: clamp(1.05rem, 2vw, 1.3rem);
+ line-height: 1.65;
+}
+
+.hero {
+ margin-bottom: 4rem;
+}
+
+.example-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 1rem;
+}
+
+.example-card {
+ position: relative;
+ min-height: 19rem;
+ padding: 1.5rem;
+ overflow: hidden;
+ border: 1px solid #293040;
+ border-radius: 1rem;
+ color: inherit;
+ background: linear-gradient(145deg, rgb(29 35 48 / 92%), rgb(17 21 29 / 92%));
+ text-decoration: none;
+ transition:
+ transform 180ms ease,
+ border-color 180ms ease;
+}
+
+.example-card:hover {
+ border-color: #5264ad;
+ transform: translateY(-3px);
+}
+
+.example-card h2 {
+ margin: 3.2rem 0 0.7rem;
+ font-size: 1.7rem;
+ letter-spacing: -0.035em;
+}
+
+.example-card p {
+ max-width: 30rem;
+ color: #9ca5b6;
+ line-height: 1.55;
+}
+
+.example-card ul {
+ display: flex;
+ gap: 0.4rem;
+ margin: 1.25rem 0 3rem;
+ padding: 0;
+ list-style: none;
+}
+
+.example-card li,
+.file-pill {
+ padding: 0.35rem 0.55rem;
+ border: 1px solid #343d50;
+ border-radius: 999px;
+ color: #aeb7c9;
+ background: #151a24;
+ font-size: 0.72rem;
+}
+
+.card-index {
+ color: #6e7d99;
+ font-family: "SFMono-Regular", Consolas, monospace;
+ font-size: 0.75rem;
+}
+
+.card-action {
+ position: absolute;
+ right: 1.5rem;
+ bottom: 1.5rem;
+ color: #94a4ff;
+ font-size: 0.82rem;
+ font-weight: 750;
+}
+
+.example-header {
+ margin-bottom: 3rem;
+}
+
+.example-header h1 {
+ font-size: clamp(2.8rem, 7vw, 5.5rem);
+}
+
+.file-pill {
+ display: inline-block;
+ margin-top: 0.5rem;
+ border-radius: 0.4rem;
+ color: #79dcb9;
+}
+
+.workbench {
+ min-height: 25rem;
+ border: 1px solid #2a3242;
+ border-radius: 1.25rem;
+ background:
+ linear-gradient(rgb(255 255 255 / 2%) 1px, transparent 1px),
+ linear-gradient(90deg, rgb(255 255 255 / 2%) 1px, transparent 1px), #111620;
+ background-size: 1.5rem 1.5rem;
+ box-shadow: 0 2rem 5rem rgb(0 0 0 / 25%);
+}
+
+.starter-panel {
+ display: grid;
+ place-items: center;
+ min-height: 25rem;
+ padding: clamp(2rem, 7vw, 5rem);
+ text-align: center;
+}
+
+.starter-panel h2 {
+ margin: 1.5rem 0 0.65rem;
+ font-size: 1.45rem;
+}
+
+.starter-panel > p {
+ max-width: 42rem;
+ margin-bottom: 0;
+ color: #99a3b5;
+ line-height: 1.65;
+}
+
+.placeholder-object {
+ position: relative;
+ width: 8rem;
+ height: 8rem;
+}
+
+.turnstile-post {
+ position: absolute;
+ bottom: 0;
+ left: 3rem;
+ width: 2rem;
+ height: 6.5rem;
+ border: 1px solid #8390a9;
+ border-radius: 0.5rem 0.5rem 0.2rem 0.2rem;
+ background: #343e51;
+}
+
+.turnstile-arm {
+ position: absolute;
+ z-index: 1;
+ top: 2.25rem;
+ left: 0.25rem;
+ width: 7.5rem;
+ height: 0.7rem;
+ border-radius: 999px;
+ background: #8092ff;
+ transform: rotate(-15deg);
+}
+
+.traffic-light {
+ display: grid;
+ gap: 0.65rem;
+ padding: 0.8rem;
+ border: 1px solid #475064;
+ border-radius: 1rem;
+ background: #202733;
+}
+
+.traffic-light span {
+ width: 3.5rem;
+ height: 3.5rem;
+ border-radius: 50%;
+ background: #303746;
+ box-shadow: inset 0 0.2rem 0.5rem rgb(0 0 0 / 45%);
+}
+
+.traffic-light .red {
+ background: #d8555f;
+ box-shadow: 0 0 2rem rgb(216 85 95 / 35%);
+}
+
+.microwave {
+ display: grid;
+ grid-template-columns: 10rem 3rem;
+ gap: 0.75rem;
+ padding: 0.8rem;
+ border: 1px solid #4a5366;
+ border-radius: 0.8rem;
+ background: #343c49;
+}
+
+.microwave-window {
+ height: 7rem;
+ border: 0.4rem solid #202630;
+ border-radius: 0.35rem;
+ background: radial-gradient(circle, #34485d, #151d27);
+}
+
+.microwave-controls {
+ display: flex;
+ flex-direction: column;
+ gap: 0.6rem;
+ align-items: center;
+ padding-top: 0.4rem;
+}
+
+.microwave-controls span {
+ width: 1.5rem;
+ height: 1.5rem;
+ border-radius: 50%;
+ background: #8793a8;
+}
+
+.file-picker {
+ display: inline-flex;
+ gap: 0.8rem;
+ align-items: center;
+ margin-bottom: 1.25rem;
+ padding: 0.65rem 0.85rem;
+ border: 1px dashed #4d5c7b;
+ border-radius: 0.65rem;
+ color: #b8c1d2;
+ cursor: pointer;
+}
+
+.file-picker input {
+ max-width: 13rem;
+ color: #7f899b;
+ font-size: 0.75rem;
+}
+
+.audio-player {
+ width: min(100%, 32rem);
+ margin-bottom: 0.5rem;
+}
+
+.transport-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 1rem;
+ width: 100%;
+ margin-bottom: 2rem;
+ text-align: left;
+}
+
+.transport-grid article {
+ min-height: 12rem;
+ padding: 1.25rem;
+ border: 1px solid #303a4d;
+ border-radius: 0.8rem;
+ background: rgb(20 26 37 / 90%);
+}
+
+.transport-grid h2 {
+ margin-top: 1.25rem;
+}
+
+.transport-grid p {
+ min-height: 2.8rem;
+ color: #98a2b5;
+ font-size: 0.85rem;
+}
+
+.status-dot {
+ display: block;
+ width: 0.7rem;
+ height: 0.7rem;
+ border-radius: 50%;
+ background: #5fddae;
+ box-shadow: 0 0 1rem rgb(95 221 174 / 60%);
+}
+
+.status-dot.channel {
+ background: #8092ff;
+ box-shadow: 0 0 1rem rgb(128 146 255 / 60%);
+}
+
+.implementation-note {
+ padding-top: 1.5rem;
+ border-top: 1px solid #2d3546;
+}
+
+@media (max-width: 48rem) {
+ .site-header {
+ display: block;
+ padding-top: 1.25rem;
+ }
+
+ nav {
+ margin-top: 0.75rem;
+ }
+
+ .example-grid,
+ .transport-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .example-card {
+ min-height: 17rem;
+ }
+}
+
diff --git a/examples/playground/tsconfig.json b/examples/playground/tsconfig.json
new file mode 100644
index 0000000..bb95bc0
--- /dev/null
+++ b/examples/playground/tsconfig.json
@@ -0,0 +1,18 @@
+{
+ "$schema": "https://json.schemastore.org/tsconfig",
+ "compilerOptions": {
+ "target": "ES2022",
+ "useDefineForClassFields": true,
+ "lib": ["ES2022", "DOM", "DOM.Iterable", "WebWorker"],
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "strict": true,
+ "skipLibCheck": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "types": ["vite/client"]
+ },
+ "include": ["src", "vite.config.ts"]
+}
diff --git a/examples/playground/vite.config.ts b/examples/playground/vite.config.ts
new file mode 100644
index 0000000..c989213
--- /dev/null
+++ b/examples/playground/vite.config.ts
@@ -0,0 +1,6 @@
+import react from "@vitejs/plugin-react"
+import { defineConfig } from "vite"
+
+export default defineConfig({
+ plugins: [react()]
+})
diff --git a/examples/pokemon/package.json b/examples/pokemon/package.json
index 440ccb3..1a79ac7 100644
--- a/examples/pokemon/package.json
+++ b/examples/pokemon/package.json
@@ -6,7 +6,8 @@
"type": "module",
"scripts": {
"dev": "vite",
- "build": "tsc --noEmit && vite build",
+ "typecheck": "tsc --noEmit",
+ "build": "pnpm typecheck && vite build",
"preview": "vite preview",
"check": "pnpm build"
},