@fluxy-chat/create-fluxy-chat 0.5.11 → 0.5.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/index.js +109 -0
  3. package/package.json +1 -1
  4. package/readme.md +11 -0
  5. package/templates/basic/package.json +1 -1
  6. package/templates/comments-board/.env.example +11 -0
  7. package/templates/comments-board/README.md +9 -0
  8. package/templates/comments-board/index.html +12 -0
  9. package/templates/comments-board/package.json +26 -0
  10. package/templates/comments-board/src/App.tsx +86 -0
  11. package/templates/comments-board/src/index.css +75 -0
  12. package/templates/comments-board/src/main.tsx +10 -0
  13. package/templates/comments-board/src/session.ts +73 -0
  14. package/templates/comments-board/src/vite-env.d.ts +13 -0
  15. package/templates/comments-board/tsconfig.json +21 -0
  16. package/templates/comments-board/vite.config.ts +7 -0
  17. package/templates/deal-room/.env.example +11 -0
  18. package/templates/deal-room/README.md +11 -0
  19. package/templates/deal-room/index.html +12 -0
  20. package/templates/deal-room/package.json +25 -0
  21. package/templates/deal-room/src/App.tsx +110 -0
  22. package/templates/deal-room/src/index.css +75 -0
  23. package/templates/deal-room/src/main.tsx +10 -0
  24. package/templates/deal-room/src/session.ts +73 -0
  25. package/templates/deal-room/src/vite-env.d.ts +13 -0
  26. package/templates/deal-room/tsconfig.json +21 -0
  27. package/templates/deal-room/vite.config.ts +7 -0
  28. package/templates/discord/package.json +1 -1
  29. package/templates/draw/.env.example +7 -0
  30. package/templates/draw/README.md +7 -0
  31. package/templates/draw/index.html +12 -0
  32. package/templates/draw/package.json +25 -0
  33. package/templates/draw/src/App.tsx +91 -0
  34. package/templates/draw/src/index.css +75 -0
  35. package/templates/draw/src/main.tsx +10 -0
  36. package/templates/draw/src/session.ts +73 -0
  37. package/templates/draw/src/vite-env.d.ts +13 -0
  38. package/templates/draw/tsconfig.json +21 -0
  39. package/templates/draw/vite.config.ts +7 -0
  40. package/templates/fleet-panel/.env.example +11 -0
  41. package/templates/fleet-panel/README.md +9 -0
  42. package/templates/fleet-panel/index.html +12 -0
  43. package/templates/fleet-panel/package.json +25 -0
  44. package/templates/fleet-panel/src/App.tsx +83 -0
  45. package/templates/fleet-panel/src/index.css +75 -0
  46. package/templates/fleet-panel/src/main.tsx +10 -0
  47. package/templates/fleet-panel/src/session.ts +73 -0
  48. package/templates/fleet-panel/src/vite-env.d.ts +13 -0
  49. package/templates/fleet-panel/tsconfig.json +21 -0
  50. package/templates/fleet-panel/vite.config.ts +7 -0
  51. package/templates/full/package.json +3 -3
  52. package/templates/game-tick/.env.example +11 -0
  53. package/templates/game-tick/README.md +9 -0
  54. package/templates/game-tick/index.html +12 -0
  55. package/templates/game-tick/package.json +25 -0
  56. package/templates/game-tick/src/App.tsx +87 -0
  57. package/templates/game-tick/src/index.css +75 -0
  58. package/templates/game-tick/src/main.tsx +10 -0
  59. package/templates/game-tick/src/session.ts +73 -0
  60. package/templates/game-tick/src/vite-env.d.ts +13 -0
  61. package/templates/game-tick/tsconfig.json +21 -0
  62. package/templates/game-tick/vite.config.ts +7 -0
  63. package/templates/iot-panel/.env.example +7 -0
  64. package/templates/iot-panel/README.md +16 -0
  65. package/templates/iot-panel/index.html +12 -0
  66. package/templates/iot-panel/package.json +25 -0
  67. package/templates/iot-panel/src/App.tsx +97 -0
  68. package/templates/iot-panel/src/index.css +75 -0
  69. package/templates/iot-panel/src/main.tsx +10 -0
  70. package/templates/iot-panel/src/session.ts +73 -0
  71. package/templates/iot-panel/src/vite-env.d.ts +13 -0
  72. package/templates/iot-panel/tsconfig.json +21 -0
  73. package/templates/iot-panel/vite.config.ts +7 -0
  74. package/templates/javascript-live-cursors/.env.example +9 -0
  75. package/templates/javascript-live-cursors/README.md +9 -0
  76. package/templates/javascript-live-cursors/index.html +17 -0
  77. package/templates/javascript-live-cursors/package.json +18 -0
  78. package/templates/javascript-live-cursors/src/index.css +31 -0
  79. package/templates/javascript-live-cursors/src/main.ts +89 -0
  80. package/templates/javascript-live-cursors/src/vite-env.d.ts +12 -0
  81. package/templates/javascript-live-cursors/tsconfig.json +13 -0
  82. package/templates/javascript-live-cursors/vite.config.ts +5 -0
  83. package/templates/live-cursors/.env.example +9 -0
  84. package/templates/live-cursors/README.md +20 -0
  85. package/templates/live-cursors/index.html +12 -0
  86. package/templates/live-cursors/package.json +25 -0
  87. package/templates/live-cursors/src/App.tsx +214 -0
  88. package/templates/live-cursors/src/index.css +94 -0
  89. package/templates/live-cursors/src/main.tsx +10 -0
  90. package/templates/live-cursors/src/vite-env.d.ts +12 -0
  91. package/templates/live-cursors/tsconfig.json +21 -0
  92. package/templates/live-cursors/vite.config.ts +7 -0
  93. package/templates/live-cursors-chat/.env.example +9 -0
  94. package/templates/live-cursors-chat/README.md +11 -0
  95. package/templates/live-cursors-chat/index.html +12 -0
  96. package/templates/live-cursors-chat/package.json +25 -0
  97. package/templates/live-cursors-chat/src/App.tsx +241 -0
  98. package/templates/live-cursors-chat/src/index.css +94 -0
  99. package/templates/live-cursors-chat/src/main.tsx +10 -0
  100. package/templates/live-cursors-chat/src/vite-env.d.ts +12 -0
  101. package/templates/live-cursors-chat/tsconfig.json +21 -0
  102. package/templates/live-cursors-chat/vite.config.ts +7 -0
  103. package/templates/minimal/package.json +2 -2
  104. package/templates/react/package.json +2 -2
  105. package/templates/slack/package.json +1 -1
  106. package/templates/telegram/package.json +1 -1
  107. package/templates/tiptap-room/.env.example +9 -0
  108. package/templates/tiptap-room/README.md +14 -0
  109. package/templates/tiptap-room/index.html +12 -0
  110. package/templates/tiptap-room/package.json +29 -0
  111. package/templates/tiptap-room/src/App.tsx +217 -0
  112. package/templates/tiptap-room/src/index.css +107 -0
  113. package/templates/tiptap-room/src/main.tsx +10 -0
  114. package/templates/tiptap-room/src/vite-env.d.ts +12 -0
  115. package/templates/tiptap-room/tsconfig.json +21 -0
  116. package/templates/tiptap-room/vite.config.ts +7 -0
  117. package/templates/voice-stage/.env.example +11 -0
  118. package/templates/voice-stage/README.md +11 -0
  119. package/templates/voice-stage/index.html +12 -0
  120. package/templates/voice-stage/package.json +25 -0
  121. package/templates/voice-stage/src/App.tsx +61 -0
  122. package/templates/voice-stage/src/index.css +75 -0
  123. package/templates/voice-stage/src/main.tsx +10 -0
  124. package/templates/voice-stage/src/session.ts +73 -0
  125. package/templates/voice-stage/src/vite-env.d.ts +13 -0
  126. package/templates/voice-stage/tsconfig.json +21 -0
  127. package/templates/voice-stage/vite.config.ts +7 -0
  128. package/templates/war-room/.env.example +11 -0
  129. package/templates/war-room/README.md +11 -0
  130. package/templates/war-room/index.html +12 -0
  131. package/templates/war-room/package.json +26 -0
  132. package/templates/war-room/src/App.tsx +120 -0
  133. package/templates/war-room/src/index.css +75 -0
  134. package/templates/war-room/src/main.tsx +10 -0
  135. package/templates/war-room/src/session.ts +73 -0
  136. package/templates/war-room/src/vite-env.d.ts +13 -0
  137. package/templates/war-room/tsconfig.json +21 -0
  138. package/templates/war-room/vite.config.ts +7 -0
  139. package/templates/whiteboard/.env.example +11 -0
  140. package/templates/whiteboard/README.md +9 -0
  141. package/templates/whiteboard/index.html +12 -0
  142. package/templates/whiteboard/package.json +26 -0
  143. package/templates/whiteboard/src/App.tsx +122 -0
  144. package/templates/whiteboard/src/index.css +75 -0
  145. package/templates/whiteboard/src/main.tsx +10 -0
  146. package/templates/whiteboard/src/session.ts +73 -0
  147. package/templates/whiteboard/src/vite-env.d.ts +13 -0
  148. package/templates/whiteboard/tsconfig.json +21 -0
  149. package/templates/whiteboard/vite.config.ts +7 -0
@@ -0,0 +1,94 @@
1
+ :root {
2
+ font-family: system-ui, -apple-system, sans-serif;
3
+ line-height: 1.5;
4
+ color: #0f172a;
5
+ background: #f8fafc;
6
+ }
7
+
8
+ * {
9
+ box-sizing: border-box;
10
+ }
11
+
12
+ body {
13
+ margin: 0;
14
+ }
15
+
16
+ .shell {
17
+ max-width: 960px;
18
+ margin: 0 auto;
19
+ padding: 1.5rem;
20
+ }
21
+
22
+ .canvas-wrap {
23
+ border: 1px solid #e2e8f0;
24
+ border-radius: 12px;
25
+ overflow: hidden;
26
+ background: #fff;
27
+ }
28
+
29
+ .chat-header {
30
+ display: flex;
31
+ justify-content: space-between;
32
+ padding: 0.75rem 1rem;
33
+ border-bottom: 1px solid #e2e8f0;
34
+ background: #0f172a;
35
+ color: #fff;
36
+ }
37
+
38
+ .status {
39
+ font-size: 0.75rem;
40
+ opacity: 0.9;
41
+ }
42
+
43
+ .canvas {
44
+ position: relative;
45
+ height: min(70vh, 560px);
46
+ background:
47
+ linear-gradient(#e2e8f0 1px, transparent 1px) 0 0 / 32px 32px,
48
+ linear-gradient(90deg, #e2e8f0 1px, transparent 1px) 0 0 / 32px 32px,
49
+ #f8fafc;
50
+ cursor: none;
51
+ touch-action: none;
52
+ }
53
+
54
+ .peer-cursor {
55
+ position: absolute;
56
+ pointer-events: none;
57
+ transform: translate(-2px, -2px);
58
+ z-index: 2;
59
+ }
60
+
61
+ .peer-cursor span {
62
+ display: block;
63
+ margin-top: 2px;
64
+ padding: 1px 6px;
65
+ border-radius: 4px;
66
+ background: currentColor;
67
+ color: #fff;
68
+ font-size: 11px;
69
+ white-space: nowrap;
70
+ }
71
+
72
+ .mode-badge {
73
+ font-size: 0.75rem;
74
+ color: #64748b;
75
+ margin: 0 0 0.75rem;
76
+ }
77
+
78
+ .error {
79
+ color: #b91c1c;
80
+ }
81
+
82
+ .room-picker {
83
+ display: flex;
84
+ flex-direction: column;
85
+ gap: 0.25rem;
86
+ margin-bottom: 1rem;
87
+ font-size: 0.875rem;
88
+ }
89
+
90
+ .room-picker input {
91
+ padding: 0.5rem 0.75rem;
92
+ border: 1px solid #cbd5e1;
93
+ border-radius: 8px;
94
+ }
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import { App } from "./App";
4
+ import "./index.css";
5
+
6
+ createRoot(document.getElementById("root")!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ );
@@ -0,0 +1,12 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ interface ImportMetaEnv {
4
+ readonly VITE_FLUXYCHAT_WORKER_URL: string;
5
+ readonly VITE_FLUXYCHAT_MEMBER_JWT?: string;
6
+ readonly VITE_FLUXYCHAT_ROOM_ID?: string;
7
+ readonly VITE_FLUXYCHAT_PUBLIC_ROOM_ID?: string;
8
+ }
9
+
10
+ interface ImportMeta {
11
+ readonly env: ImportMetaEnv;
12
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+ "moduleResolution": "bundler",
9
+ "allowImportingTsExtensions": true,
10
+ "isolatedModules": true,
11
+ "moduleDetection": "force",
12
+ "noEmit": true,
13
+ "jsx": "react-jsx",
14
+ "strict": true,
15
+ "noUnusedLocals": true,
16
+ "noUnusedParameters": true,
17
+ "noFallthroughCasesInSwitch": true,
18
+ "noUncheckedSideEffectImports": true
19
+ },
20
+ "include": ["src"]
21
+ }
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from "vite";
2
+ import react from "@vitejs/plugin-react";
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ server: { port: 5173 },
7
+ });
@@ -11,8 +11,8 @@
11
11
  "dependencies": {
12
12
  "@fluxy-chat/ui-kit": "^0.1.1",
13
13
  "@fluxy-chat/ui": "^0.1.4",
14
- "@fluxy-chat/react": "^0.1.3",
15
- "@fluxy-chat/sdk": "^0.6.3",
14
+ "@fluxy-chat/react": "^0.1.4",
15
+ "@fluxy-chat/sdk": "^0.6.4",
16
16
  "react": "^19.0.0",
17
17
  "react-dom": "^19.0.0"
18
18
  },
@@ -9,8 +9,8 @@
9
9
  "preview": "vite preview"
10
10
  },
11
11
  "dependencies": {
12
- "@fluxy-chat/react": "^0.1.3",
13
- "@fluxy-chat/sdk": "^0.6.3",
12
+ "@fluxy-chat/react": "^0.1.4",
13
+ "@fluxy-chat/sdk": "^0.6.4",
14
14
  "react": "^19.0.0",
15
15
  "react-dom": "^19.0.0",
16
16
  "zustand": "^5.0.0"
@@ -9,7 +9,7 @@
9
9
  "type-check": "tsc --noEmit"
10
10
  },
11
11
  "dependencies": {
12
- "@fluxy-chat/sdk": "^0.6.3",
12
+ "@fluxy-chat/sdk": "^0.6.4",
13
13
  "@slack/bolt": "^4.0.0",
14
14
  "@slack/web-api": "^7.0.0"
15
15
  },
@@ -9,7 +9,7 @@
9
9
  "type-check": "tsc --noEmit"
10
10
  },
11
11
  "dependencies": {
12
- "@fluxy-chat/sdk": "^0.6.3",
12
+ "@fluxy-chat/sdk": "^0.6.4",
13
13
  "node-telegram-bot-api": "^0.66.0"
14
14
  },
15
15
  "devDependencies": {
@@ -0,0 +1,9 @@
1
+ # FluxyChat worker URL (hosted or self-hosted)
2
+ VITE_FLUXYCHAT_WORKER_URL=https://your-worker.example.workers.dev
3
+
4
+ # Option A — member JWT (~2 min via onboarding)
5
+ VITE_FLUXYCHAT_MEMBER_JWT=eyJ...
6
+ VITE_FLUXYCHAT_ROOM_ID=demo
7
+
8
+ # Option B — public guest room (~60s, no signup)
9
+ # VITE_FLUXYCHAT_PUBLIC_ROOM_ID=your-public-room-id
@@ -0,0 +1,14 @@
1
+ # FluxyChat Tiptap room
2
+
3
+ Yjs Storage + Tiptap on the room Durable Object (binary WS, `yjs-sync.js`). Not a second CRDT. Open **two tabs**.
4
+
5
+ ```bash
6
+ npx @fluxy-chat/create-fluxy-chat@latest my-doc --example tiptap-room
7
+ cp .env.example .env
8
+ # VITE_FLUXYCHAT_WORKER_URL + PUBLIC_ROOM_ID or MEMBER_JWT
9
+ npm run dev
10
+ ```
11
+
12
+ ## Code
13
+
14
+ `FluxyYjsProvider` + `useStorage` / `useMutation` from `@fluxy-chat/sdk/yjs`. Tiptap `Collaboration` uses `useYjsDoc()` and `field: "prosemirror"`. LiveFile refs go through `uploadLiveFile` (existing `POST /upload`).
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>FluxyChat Tiptap room</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "fluxy-tiptap-room",
3
+ "private": true,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc -b && vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "@fluxy-chat/react": "^0.1.4",
13
+ "@fluxy-chat/sdk": "^0.6.4",
14
+ "@tiptap/extension-collaboration": "^2.11.5",
15
+ "@tiptap/pm": "^2.11.5",
16
+ "@tiptap/react": "^2.11.5",
17
+ "@tiptap/starter-kit": "^2.11.5",
18
+ "react": "^19.0.0",
19
+ "react-dom": "^19.0.0",
20
+ "yjs": "^13.6.31"
21
+ },
22
+ "devDependencies": {
23
+ "@types/react": "^19.0.0",
24
+ "@types/react-dom": "^19.0.0",
25
+ "@vitejs/plugin-react": "^4.3.0",
26
+ "typescript": "^5.6.0",
27
+ "vite": "^6.0.0"
28
+ }
29
+ }
@@ -0,0 +1,217 @@
1
+ import { useEffect, useState } from "react";
2
+ import { EditorContent, useEditor } from "@tiptap/react";
3
+ import StarterKit from "@tiptap/starter-kit";
4
+ import Collaboration from "@tiptap/extension-collaboration";
5
+ import { FluxyChatClient } from "@fluxy-chat/sdk";
6
+ import {
7
+ FLUXY_YJS_EDITOR_FRAGMENT,
8
+ FluxyYjsProvider,
9
+ isLiveFile,
10
+ uploadLiveFile,
11
+ useMutation,
12
+ useRedo,
13
+ useStorage,
14
+ useUndo,
15
+ useYjsContext,
16
+ type FluxyLiveFile,
17
+ } from "@fluxy-chat/sdk/yjs";
18
+
19
+ const workerUrl = import.meta.env.VITE_FLUXYCHAT_WORKER_URL?.trim();
20
+ const memberJwt = import.meta.env.VITE_FLUXYCHAT_MEMBER_JWT?.trim();
21
+ const publicRoomId = import.meta.env.VITE_FLUXYCHAT_PUBLIC_ROOM_ID?.trim();
22
+ const configuredRoomId = import.meta.env.VITE_FLUXYCHAT_ROOM_ID?.trim() || "demo";
23
+
24
+ interface FluxySession {
25
+ workerUrl: string;
26
+ token: string;
27
+ userId: string;
28
+ roomId: string;
29
+ mode: "member" | "guest";
30
+ }
31
+
32
+ function useFluxySession(): {
33
+ session: FluxySession | null;
34
+ loading: boolean;
35
+ error: string | null;
36
+ } {
37
+ const [session, setSession] = useState<FluxySession | null>(null);
38
+ const [loading, setLoading] = useState(Boolean(workerUrl));
39
+ const [error, setError] = useState<string | null>(null);
40
+
41
+ useEffect(() => {
42
+ if (!workerUrl) {
43
+ setLoading(false);
44
+ return;
45
+ }
46
+
47
+ if (memberJwt) {
48
+ setSession({
49
+ workerUrl,
50
+ token: memberJwt,
51
+ userId: "demo-user",
52
+ roomId: configuredRoomId,
53
+ mode: "member",
54
+ });
55
+ setLoading(false);
56
+ return;
57
+ }
58
+
59
+ if (publicRoomId) {
60
+ let cancelled = false;
61
+ void FluxyChatClient.joinPublicRoomAsGuest(workerUrl, publicRoomId, {
62
+ displayName: "Guest",
63
+ })
64
+ .then((guest) => {
65
+ if (cancelled) return;
66
+ setSession({
67
+ workerUrl,
68
+ token: guest.token,
69
+ userId: guest.userId,
70
+ roomId: guest.roomId,
71
+ mode: "guest",
72
+ });
73
+ })
74
+ .catch((err) => {
75
+ if (!cancelled) {
76
+ setError(err instanceof Error ? err.message : "Guest session failed");
77
+ }
78
+ })
79
+ .finally(() => {
80
+ if (!cancelled) setLoading(false);
81
+ });
82
+ return () => {
83
+ cancelled = true;
84
+ };
85
+ }
86
+
87
+ setLoading(false);
88
+ }, []);
89
+
90
+ return { session, loading, error };
91
+ }
92
+
93
+ function DocBoard() {
94
+ const { doc, connected, client, roomId } = useYjsContext();
95
+ const title = useStorage((root) => (typeof root.title === "string" ? root.title : "Untitled"));
96
+ const hero = useStorage((root) => (isLiveFile(root.hero) ? (root.hero as FluxyLiveFile) : null));
97
+ const setTitle = useMutation((storage, next: string) => {
98
+ storage.set("title", next);
99
+ }, []);
100
+ const setHero = useMutation((storage, file: FluxyLiveFile) => {
101
+ storage.set("hero", file);
102
+ }, []);
103
+ const undo = useUndo();
104
+ const redo = useRedo();
105
+
106
+ const editor = useEditor({
107
+ extensions: [
108
+ StarterKit.configure({ history: false }),
109
+ Collaboration.configure({
110
+ document: doc,
111
+ field: FLUXY_YJS_EDITOR_FRAGMENT,
112
+ }),
113
+ ],
114
+ });
115
+
116
+ async function onPickFile(file: File | undefined) {
117
+ if (!file) return;
118
+ const live = await uploadLiveFile(client, roomId, file);
119
+ setHero(live);
120
+ }
121
+
122
+ return (
123
+ <>
124
+ <div className="chat-header">
125
+ <strong>Tiptap room</strong>
126
+ <span className="status">{connected ? "Yjs connected" : "connecting…"}</span>
127
+ </div>
128
+ <div className="toolbar">
129
+ <input
130
+ type="text"
131
+ value={title}
132
+ onChange={(event) => setTitle(event.target.value)}
133
+ aria-label="Document title"
134
+ />
135
+ <button type="button" onClick={() => undo()}>
136
+ Undo
137
+ </button>
138
+ <button type="button" onClick={() => redo()}>
139
+ Redo
140
+ </button>
141
+ <input
142
+ type="file"
143
+ accept="image/*"
144
+ onChange={(event) => void onPickFile(event.target.files?.[0])}
145
+ />
146
+ </div>
147
+ <div className="editor">
148
+ {editor ? <EditorContent editor={editor} /> : <p>Loading editor…</p>}
149
+ {hero ? <img className="hero" src={hero.url} alt={hero.name} /> : null}
150
+ </div>
151
+ </>
152
+ );
153
+ }
154
+
155
+ export function App() {
156
+ const { session, loading, error } = useFluxySession();
157
+ const [roomOverride, setRoomOverride] = useState("");
158
+
159
+ if (!workerUrl) {
160
+ return (
161
+ <div className="shell">
162
+ <p>Set <code>VITE_FLUXYCHAT_WORKER_URL</code> in <code>.env</code>.</p>
163
+ </div>
164
+ );
165
+ }
166
+
167
+ if (loading) {
168
+ return (
169
+ <div className="shell">
170
+ <p>Starting session…</p>
171
+ </div>
172
+ );
173
+ }
174
+
175
+ if (error) {
176
+ return (
177
+ <div className="shell">
178
+ <p className="error">{error}</p>
179
+ </div>
180
+ );
181
+ }
182
+
183
+ if (!session) {
184
+ return (
185
+ <div className="shell">
186
+ <p>Set a member JWT or a public room id in <code>.env</code>.</p>
187
+ </div>
188
+ );
189
+ }
190
+
191
+ const roomId = roomOverride.trim() || session.roomId;
192
+
193
+ return (
194
+ <div className="shell">
195
+ <p className="mode-badge">
196
+ {session.mode} · room <code>{roomId}</code> · open two tabs
197
+ </p>
198
+ <label className="room-picker">
199
+ Room id
200
+ <input
201
+ value={roomOverride}
202
+ placeholder={session.roomId}
203
+ onChange={(event) => setRoomOverride(event.target.value)}
204
+ />
205
+ </label>
206
+ <FluxyYjsProvider
207
+ key={roomId}
208
+ workerUrl={session.workerUrl}
209
+ token={session.token}
210
+ userId={session.userId}
211
+ roomId={roomId}
212
+ >
213
+ <DocBoard />
214
+ </FluxyYjsProvider>
215
+ </div>
216
+ );
217
+ }
@@ -0,0 +1,107 @@
1
+ :root {
2
+ font-family: system-ui, -apple-system, sans-serif;
3
+ line-height: 1.5;
4
+ color: #0f172a;
5
+ background: #f8fafc;
6
+ }
7
+
8
+ * {
9
+ box-sizing: border-box;
10
+ }
11
+
12
+ body {
13
+ margin: 0;
14
+ }
15
+
16
+ .shell {
17
+ max-width: 800px;
18
+ margin: 0 auto;
19
+ padding: 1.5rem;
20
+ }
21
+
22
+ .chat-header {
23
+ display: flex;
24
+ justify-content: space-between;
25
+ padding: 0.75rem 1rem;
26
+ border: 1px solid #e2e8f0;
27
+ border-bottom: 0;
28
+ border-radius: 12px 12px 0 0;
29
+ background: #0f172a;
30
+ color: #fff;
31
+ }
32
+
33
+ .status {
34
+ font-size: 0.75rem;
35
+ opacity: 0.9;
36
+ }
37
+
38
+ .toolbar {
39
+ display: flex;
40
+ flex-wrap: wrap;
41
+ gap: 0.5rem;
42
+ align-items: center;
43
+ padding: 0.75rem 1rem;
44
+ border: 1px solid #e2e8f0;
45
+ border-bottom: 0;
46
+ background: #fff;
47
+ }
48
+
49
+ .toolbar input[type="text"] {
50
+ flex: 1;
51
+ min-width: 12rem;
52
+ padding: 0.4rem 0.6rem;
53
+ border: 1px solid #cbd5e1;
54
+ border-radius: 8px;
55
+ }
56
+
57
+ .toolbar button {
58
+ padding: 0.4rem 0.75rem;
59
+ border: 0;
60
+ border-radius: 8px;
61
+ background: #2563eb;
62
+ color: #fff;
63
+ cursor: pointer;
64
+ }
65
+
66
+ .editor {
67
+ min-height: 280px;
68
+ padding: 1rem 1.25rem;
69
+ border: 1px solid #e2e8f0;
70
+ border-radius: 0 0 12px 12px;
71
+ background: #fff;
72
+ }
73
+
74
+ .editor .tiptap {
75
+ outline: none;
76
+ min-height: 240px;
77
+ }
78
+
79
+ .hero {
80
+ margin: 0.75rem 0 0;
81
+ max-width: 100%;
82
+ border-radius: 8px;
83
+ }
84
+
85
+ .mode-badge {
86
+ font-size: 0.75rem;
87
+ color: #64748b;
88
+ margin: 0 0 0.75rem;
89
+ }
90
+
91
+ .error {
92
+ color: #b91c1c;
93
+ }
94
+
95
+ .room-picker {
96
+ display: flex;
97
+ flex-direction: column;
98
+ gap: 0.25rem;
99
+ margin-bottom: 1rem;
100
+ font-size: 0.875rem;
101
+ }
102
+
103
+ .room-picker input {
104
+ padding: 0.5rem 0.75rem;
105
+ border: 1px solid #cbd5e1;
106
+ border-radius: 8px;
107
+ }
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import { App } from "./App";
4
+ import "./index.css";
5
+
6
+ createRoot(document.getElementById("root")!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ );
@@ -0,0 +1,12 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ interface ImportMetaEnv {
4
+ readonly VITE_FLUXYCHAT_WORKER_URL: string;
5
+ readonly VITE_FLUXYCHAT_MEMBER_JWT?: string;
6
+ readonly VITE_FLUXYCHAT_ROOM_ID?: string;
7
+ readonly VITE_FLUXYCHAT_PUBLIC_ROOM_ID?: string;
8
+ }
9
+
10
+ interface ImportMeta {
11
+ readonly env: ImportMetaEnv;
12
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+ "moduleResolution": "bundler",
9
+ "allowImportingTsExtensions": true,
10
+ "isolatedModules": true,
11
+ "moduleDetection": "force",
12
+ "noEmit": true,
13
+ "jsx": "react-jsx",
14
+ "strict": true,
15
+ "noUnusedLocals": true,
16
+ "noUnusedParameters": true,
17
+ "noFallthroughCasesInSwitch": true,
18
+ "noUncheckedSideEffectImports": true
19
+ },
20
+ "include": ["src"]
21
+ }
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from "vite";
2
+ import react from "@vitejs/plugin-react";
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ server: { port: 5173 },
7
+ });
@@ -0,0 +1,11 @@
1
+ # FluxyChat worker URL (hosted or self-hosted)
2
+ VITE_FLUXYCHAT_WORKER_URL=https://your-worker.example.workers.dev
3
+
4
+ # Option A — member JWT
5
+ VITE_FLUXYCHAT_MEMBER_JWT=eyJ...
6
+ VITE_FLUXYCHAT_ROOM_ID=demo
7
+ # Optional — room-peer agent on the timeline
8
+ # VITE_FLUXYCHAT_AGENT_ID=agt_...
9
+
10
+ # Option B — public guest room
11
+ # VITE_FLUXYCHAT_PUBLIC_ROOM_ID=your-public-room-id
@@ -0,0 +1,11 @@
1
+ # FluxyChat voice stage
2
+
3
+ `joinVoiceStage` is **signaling on the room WebSocket** (who is speaker/listener). It is not live WebRTC audio and does not publish latency numbers.
4
+
5
+ Async clips: `POST /messages/voice`.
6
+
7
+ ```bash
8
+ npx @fluxy-chat/create-fluxy-chat@latest my-stage --example voice-stage
9
+ cp .env.example .env
10
+ npm run dev
11
+ ```
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>FluxyChat voice stage</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>