diff --git a/.gitignore b/.gitignore
index efd1034..f0313e6 100755
--- a/.gitignore
+++ b/.gitignore
@@ -115,4 +115,5 @@ public/projects/*
public/data/*
-storage/
\ No newline at end of file
+storage/
+yarn.lock
\ No newline at end of file
diff --git a/flux/.gitignore b/flux/.gitignore
new file mode 100644
index 0000000..286b096
--- /dev/null
+++ b/flux/.gitignore
@@ -0,0 +1,21 @@
+node_modules
+dist
+package-lock.json
+
+# Log files
+*.log
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+*.turbo
+dev-dist
+vite.config.ts.timestamp*
+
+cachedb
+yarn.lock
\ No newline at end of file
diff --git a/flux/README b/flux/README
new file mode 100644
index 0000000..6ec7cc8
--- /dev/null
+++ b/flux/README
@@ -0,0 +1,17 @@
+# Create Flux Plugin
+
+Bootstrap a new [Flux](https://fluxsocial.io) plugin for your community.
+
+[Documentation](https://docs.fluxsocial.io/create-flux-plugin/getting-started/introduction.html)
+
+Install deps and start building:
+
+```bash [npm]
+cd [plugin-name]
+npm install
+npm run dev
+```
+
+## Prerequisites
+
+Flux runs on top of [AD4M](https://ad4m.dev), a p2p framework where all data is stored and shared. In order to build a new Flux app you need to [download](https://ad4m.dev/download) and install AD4M.
diff --git a/flux/globals.d.ts b/flux/globals.d.ts
new file mode 100644
index 0000000..1eabbb4
--- /dev/null
+++ b/flux/globals.d.ts
@@ -0,0 +1 @@
+declare module "*.module.css";
diff --git a/flux/index.html b/flux/index.html
new file mode 100644
index 0000000..51d5739
--- /dev/null
+++ b/flux/index.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+ Flux App
+
+
+
+
+
+
+
+
+
+
diff --git a/flux/package.json b/flux/package.json
new file mode 100644
index 0000000..1e32688
--- /dev/null
+++ b/flux/package.json
@@ -0,0 +1,53 @@
+{
+ "name": "@conjureworld/flux-plugin",
+ "version": "0.0.1",
+ "fluxapp": {
+ "name": "Conjure",
+ "description": "Immersive Collaboration",
+ "icon": "conjure"
+ },
+ "keywords": [
+ "flux-plugin",
+ "ad4m-view"
+ ],
+ "type": "module",
+ "files": [
+ "dist"
+ ],
+ "main": "./dist/main.umd.cjs",
+ "module": "./dist/main.js",
+ "exports": {
+ ".": {
+ "import": "./dist/main.js",
+ "require": "./dist/main.umd.cjs"
+ }
+ },
+ "scripts": {
+ "start": "vite",
+ "dev": "vite",
+ "build": "vite build",
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "ISC",
+ "description": "",
+ "overrides": {
+ "@coasys/ad4m": "0.10.1-release-candidate-4",
+ "@coasys/ad4m-react-hooks": "0.10.1-release-candidate-4",
+ "@coasys/flux-api": "0.10.1-rc4"
+ },
+ "dependencies": {
+ "@coasys/ad4m": "0.10.1-release-candidate-4",
+ "@coasys/ad4m-react-hooks": "0.10.1-release-candidate-4",
+ "@coasys/flux-api": "0.10.1-rc4"
+ },
+ "devDependencies": {
+ "@babel/plugin-proposal-class-properties": "^7.18.6",
+ "@babel/plugin-proposal-decorators": "^7.21.0",
+ "@babel/plugin-transform-private-methods": "^7.27.1",
+ "@coasys/flux-container": "0.10.1-rc4",
+ "@vitejs/plugin-react-swc": "^4.0.1",
+ "vite-plugin-babel": "1.3.2",
+ "vite-plugin-css-injected-by-js": "^3.5.2"
+ }
+}
diff --git a/flux/src/App.tsx b/flux/src/App.tsx
new file mode 100644
index 0000000..44cc09b
--- /dev/null
+++ b/flux/src/App.tsx
@@ -0,0 +1,125 @@
+// tslint:disable:ordered-imports
+
+import '@ir-engine/engine'
+
+import { startTimer } from '@ir-engine/spatial/src/startTimer'
+
+import { PerspectiveProxy } from '@coasys/ad4m'
+import { AgentClient } from '@coasys/ad4m/lib/src/agent/AgentClient'
+import {
+ createEngine,
+ EngineState,
+ EntityID,
+ getComponent,
+ setComponent,
+ SourceID,
+ UUIDComponent
+} from '@ir-engine/ecs'
+import { dispatchAction, getMutableState, getState, useMutableState, type UserID } from '@ir-engine/hyperflux'
+import { ReferenceSpaceState, TransformComponent } from '@ir-engine/spatial'
+import { useSpatialEngine } from '@ir-engine/spatial/src/initializeEngine'
+import { useEngineCanvas } from '@ir-engine/spatial/src/renderer/functions/useEngineCanvas'
+import React, { useEffect, useRef, useState } from 'react'
+import { SceneState } from '@ir-engine/engine/src/gltf/GLTFState'
+import { LocationState } from '@ir-engine/client-core/src/social/services/LocationService'
+import { SpectateActions } from '@ir-engine/spatial/src/camera/systems/SpectateSystem'
+
+import { useTestScene } from '../../src/world/TestScene'
+import { CameraOrbitComponent } from '@ir-engine/spatial/src/camera/components/CameraOrbitComponent'
+import { InputComponent } from '@ir-engine/spatial/src/input/components/InputComponent'
+import { Box3, Vector3 } from 'three'
+// import { useBasicScene } from '../../src/world/BasicScene'
+// import { useSpawnAvatar } from '../../src/world/useSpawnAvatar'
+
+createEngine()
+startTimer()
+
+type Props = {
+ agent: AgentClient
+ perspective: PerspectiveProxy
+ source: string
+}
+
+const Scene = (props: { url: string }) => {
+ useTestScene(props.url)
+ // useBasicScene(props.url)
+ // useSpawnAvatar(props.url)
+ useEffect(() => {
+ const viewer = getState(ReferenceSpaceState).viewerEntity
+ setComponent(viewer, CameraOrbitComponent)
+ setComponent(viewer, InputComponent)
+
+ const transform = getComponent(viewer, TransformComponent)
+ transform.rotation.setFromAxisAngle(new Vector3(0, 1, 0), -Math.PI / 2)
+
+ CameraOrbitComponent.setFocus(
+ viewer,
+ new Vector3(0, 1.5, 0),
+ new Box3().setFromCenterAndSize(new Vector3(0, 1.5, 0), new Vector3(3, 3, 3))
+ )
+
+ const sceneURL = `${props.url}.gltf`
+ getMutableState(LocationState).currentLocation.location.sceneURL.set(sceneURL)
+ return () => {
+ getMutableState(LocationState).currentLocation.location.sceneURL.set('')
+ }
+ }, [])
+ return null
+}
+
+const Engine = (props: Props) => {
+ const ref = useRef(null)
+
+ useSpatialEngine()
+ useEngineCanvas(ref.current)
+
+ const viewerEntity = useMutableState(ReferenceSpaceState).viewerEntity.value
+
+ return (
+ <>
+
+ {viewerEntity && }
+ >
+ )
+}
+
+export default function App({ agent, perspective, source }: Props) {
+ if (!perspective?.uuid || !agent) return "No perspective or agent client"
+
+ const [ready, setReady] = useState(false)
+
+ useEffect(() => {
+ if (!agent) return
+ agent.me().then((me) => {
+ setReady(true)
+ getMutableState(EngineState).userID.set(me.did as UserID)
+ })
+ }, [])
+
+ return (
+
+ {ready && }
+
+
+ )
+}
diff --git a/flux/src/main.ts b/flux/src/main.ts
new file mode 100644
index 0000000..ebeaef0
--- /dev/null
+++ b/flux/src/main.ts
@@ -0,0 +1,12 @@
+import '@hookstate/core'
+import * as React from 'react'
+import * as ReactDOM from 'react-dom/client'
+import App from './App'
+import { reactToWebComponent } from './reactToWebComponent'
+
+const CustomElement = reactToWebComponent(App, React, ReactDOM, {
+ shadow: false,
+ observedProps: ['perspective', 'agent', 'source']
+})
+
+export default CustomElement
diff --git a/flux/src/reactToWebComponent.ts b/flux/src/reactToWebComponent.ts
new file mode 100644
index 0000000..0d7de3b
--- /dev/null
+++ b/flux/src/reactToWebComponent.ts
@@ -0,0 +1,229 @@
+// Minimal runtime contract for React/ReactDOM (so this file is framework-agnostic)
+type ReactLike = {
+ createElement: (...args: any[]) => any
+}
+type ReactDOMLike = {
+ createRoot: (container: Element | DocumentFragment) => {
+ render: (el: any) => void
+ unmount: () => void
+ }
+}
+
+type Options = {
+ /** Tag name to register (you can also call `customElements.define` yourself). */
+ tagName?: string
+ /** Use Shadow DOM? If false, render into the element itself. */
+ shadow?: boolean | ShadowRootInit
+ /** Attribute prefix to treat as props (e.g. data-foo -> foo). Empty = all attributes. */
+ attrPrefix?: string // default: '' (accept all)
+ /** Element property names to expose as pass-through React props (object/class friendly). */
+ observedProps?: string[]
+ /** Initial props (merged shallowly before anything else). */
+ initialProps?: Record
+ /** Optional styles to inject (adoptedStyleSheets or