@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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.13] - 2026-08-27
4
+
5
+ ### Changed
6
+
7
+ - Templates pin `@fluxy-chat/sdk@^0.6.4` (game `listLeaderboard`). Publish `@fluxy-chat/sdk@0.6.4` **before** this CLI, or `pnpm create` / StackBlitz will miss that version.
8
+
9
+ ## [0.5.12] - 2026-08-26
10
+
11
+ ### Added
12
+
13
+ - Gallery `--example` apps: `live-cursors`, `live-cursors-chat`, `javascript-live-cursors`, `tiptap-room`, `whiteboard`, `draw`, `comments-board`, `war-room`, `iot-panel`, `deal-room`, `fleet-panel`, `game-tick`, `voice-stage`.
14
+
15
+ ### Changed
16
+
17
+ - Templates that use React pin `@fluxy-chat/react@^0.1.4`. Publish `@fluxy-chat/protocol@0.1.5` and `@fluxy-chat/react@0.1.4` **before** this CLI, or `pnpm create` / StackBlitz will miss those versions.
18
+
3
19
  ## [0.5.11] - 2026-08-25
4
20
 
5
21
  ### Added
package/dist/index.js CHANGED
@@ -875,6 +875,21 @@ import { exec } from "child_process";
875
875
  import { promisify } from "util";
876
876
  var execAsync = promisify(exec);
877
877
  var TEMPLATE_CHOICES = "react, full, basic, slack, telegram, discord, web, hr-feedback";
878
+ var GALLERY_EXAMPLES = [
879
+ "live-cursors",
880
+ "live-cursors-chat",
881
+ "javascript-live-cursors",
882
+ "tiptap-room",
883
+ "war-room",
884
+ "iot-panel",
885
+ "draw",
886
+ "deal-room",
887
+ "fleet-panel",
888
+ "game-tick",
889
+ "voice-stage",
890
+ "comments-board",
891
+ "whiteboard"
892
+ ];
878
893
  function parseArgs(argv) {
879
894
  const args = {
880
895
  yes: false,
@@ -896,6 +911,13 @@ function parseArgs(argv) {
896
911
  } else if (arg === "--full") {
897
912
  args.full = true;
898
913
  args.adapter = "full";
914
+ } else if (arg === "--example") {
915
+ const value = argv[++i]?.trim();
916
+ if (!value || !GALLERY_EXAMPLES.includes(value)) {
917
+ console.error(`Invalid example: ${value ?? ""}. Choose: ${GALLERY_EXAMPLES.join(", ")}`);
918
+ process.exit(1);
919
+ }
920
+ args.example = value;
899
921
  } else if (arg === "--mode") {
900
922
  const value = argv[++i]?.trim().toLowerCase();
901
923
  if (value === "local" || value === "hosted" || value === "self-host") {
@@ -975,6 +997,7 @@ ${pc.bold("Options:")}
975
997
  hosted = Clerk on fluxychat.com (no wrangler)
976
998
  local / self-host = your Worker (asks for URL + keys)
977
999
  --minimal Chat-only widget (ui-kit)
1000
+ --example <name> Gallery app: ${GALLERY_EXAMPLES.join(", ")}
978
1001
  --skip-install Skip dependency installation
979
1002
  --no-git Skip git repository initialization
980
1003
  -h, --help Show this help
@@ -982,6 +1005,10 @@ ${pc.bold("Options:")}
982
1005
  ${pc.bold("Examples:")}
983
1006
  ${pc.cyan("npx @fluxy-chat/create-fluxy-chat@latest my-app --mode hosted -y")}
984
1007
  ${pc.cyan("npx @fluxy-chat/create-fluxy-chat@latest my-app --mode self-host")}
1008
+ ${pc.cyan("npx @fluxy-chat/create-fluxy-chat@latest my-cursors --example live-cursors")}
1009
+ ${pc.cyan("npx @fluxy-chat/create-fluxy-chat@latest my-doc --example tiptap-room")}
1010
+ ${pc.cyan("npx @fluxy-chat/create-fluxy-chat@latest my-war --example war-room")}
1011
+ ${pc.cyan("npx @fluxy-chat/create-fluxy-chat@latest my-iot --example iot-panel")}
985
1012
  ${pc.cyan("npx @fluxy-chat/create-fluxy-chat@latest my-chat --minimal")}
986
1013
  ${pc.cyan("npx @fluxy-chat/create-fluxy-chat@latest my-bot --adapter slack")}
987
1014
  `;
@@ -992,6 +1019,88 @@ async function main() {
992
1019
  process.exit(0);
993
1020
  }
994
1021
  intro(pc.bgCyan(pc.black(" create-fluxy-chat ")));
1022
+ if (args.example) {
1023
+ const exampleName = args.example;
1024
+ const projectName = args.name ?? `my-${exampleName}`;
1025
+ const nameError = validateProjectName(projectName);
1026
+ if (nameError) {
1027
+ outro(pc.red(nameError));
1028
+ process.exitCode = 1;
1029
+ return;
1030
+ }
1031
+ const projectDir2 = path2.resolve(process.cwd(), projectName);
1032
+ if (fs2.existsSync(projectDir2) && fs2.readdirSync(projectDir2).length > 0) {
1033
+ outro(pc.red(`Directory "${projectName}" already exists and is not empty.`));
1034
+ process.exitCode = 1;
1035
+ return;
1036
+ }
1037
+ const s2 = spinner();
1038
+ s2.start(`Copying example "${exampleName}"`);
1039
+ const templateRoot = path2.join(templatesDir(), exampleName);
1040
+ if (!fs2.existsSync(templateRoot)) {
1041
+ s2.stop("Example template missing.");
1042
+ outro(pc.red(`No template at ${templateRoot}`));
1043
+ process.exitCode = 1;
1044
+ return;
1045
+ }
1046
+ fs2.mkdirSync(projectDir2, { recursive: true });
1047
+ copyDir(templateRoot, projectDir2);
1048
+ const pkgPath = path2.join(projectDir2, "package.json");
1049
+ const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf8"));
1050
+ pkg.name = projectName;
1051
+ writeJson(projectDir2, "package.json", pkg);
1052
+ s2.stop(`Example "${exampleName}" created.`);
1053
+ if (!args.noGit) {
1054
+ const gitSpinner = spinner();
1055
+ gitSpinner.start("Initializing git repository");
1056
+ try {
1057
+ await execAsync("git init", { cwd: projectDir2 });
1058
+ gitSpinner.stop("Git repository initialized.");
1059
+ } catch {
1060
+ gitSpinner.stop("Failed to initialize git repository.");
1061
+ }
1062
+ }
1063
+ if (!args.skipInstall) {
1064
+ const pm2 = args.pm ?? detectPackageManager(process.env.npm_config_user_agent ?? "");
1065
+ const installSpinner = spinner();
1066
+ installSpinner.start(`Installing dependencies with ${pm2}`);
1067
+ try {
1068
+ await execAsync(installCommand(pm2), { cwd: projectDir2 });
1069
+ installSpinner.stop("Dependencies installed.");
1070
+ } catch {
1071
+ installSpinner.stop("Failed to install dependencies.");
1072
+ log.warning(`Run "${installCommand(pm2)}" manually in the project directory.`);
1073
+ }
1074
+ }
1075
+ const pm = args.pm ?? detectPackageManager(process.env.npm_config_user_agent ?? "");
1076
+ const devCmd2 = pm === "npm" ? "npm run" : pm;
1077
+ const tryHints = {
1078
+ "tiptap-room": "# Open two tabs \u2014 type in the editor",
1079
+ "war-room": "# Open two tabs \u2014 chat; set AGENT_ID to invokeAgent",
1080
+ "iot-panel": "# Keep this tab open; curl an ingest from another terminal",
1081
+ draw: "# Open two tabs \u2014 move and click",
1082
+ "deal-room": "# Open two tabs \u2014 propose a decision and ack from both",
1083
+ "fleet-panel": "# Keep this tab open; click Post sample GPS",
1084
+ "game-tick": "# Matchmake + start, then Submit input (not netcode)",
1085
+ "voice-stage": "# Open two tabs \u2014 join speaker/listener (signaling, not WebRTC)",
1086
+ "comments-board": "# Click the canvas to pin a thread",
1087
+ "live-cursors-chat": "# Open two tabs \u2014 move and chat",
1088
+ whiteboard: "# Open two tabs \u2014 draw strokes (Yjs, not a second CRDT)"
1089
+ };
1090
+ const tryHint = tryHints[exampleName] ?? "# Open two tabs \u2014 move the pointer";
1091
+ note(
1092
+ [
1093
+ `cd ${projectName}`,
1094
+ "cp .env.example .env",
1095
+ "# Set VITE_FLUXYCHAT_WORKER_URL + public room ID or member JWT",
1096
+ `${devCmd2} dev`,
1097
+ tryHint
1098
+ ].join("\n"),
1099
+ "Next steps"
1100
+ );
1101
+ outro(`${pc.green("Done!")} Visit ${pc.cyan("https://docs.fluxychat.com/llms.txt")}`);
1102
+ return;
1103
+ }
995
1104
  const config = await runPrompts({
996
1105
  name: args.name,
997
1106
  adapter: args.adapter,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluxy-chat/create-fluxy-chat",
3
- "version": "0.5.11",
3
+ "version": "0.5.13",
4
4
  "description": "Scaffold a new FluxyChat bot project with a single command",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/readme.md CHANGED
@@ -26,6 +26,16 @@ Self-host writes `.fluxy/worker.dev.vars` (Worker URL, Groq key, signing key). M
26
26
  ```bash
27
27
  npx @fluxy-chat/create-fluxy-chat@latest my-app --mode hosted -y
28
28
  npx @fluxy-chat/create-fluxy-chat@latest my-app --full -y
29
+ npx @fluxy-chat/create-fluxy-chat@latest my-cursors --example live-cursors
30
+ npx @fluxy-chat/create-fluxy-chat@latest my-doc --example tiptap-room
31
+ npx @fluxy-chat/create-fluxy-chat@latest my-war --example war-room
32
+ npx @fluxy-chat/create-fluxy-chat@latest my-iot --example iot-panel
33
+ npx @fluxy-chat/create-fluxy-chat@latest my-draw --example draw
34
+ npx @fluxy-chat/create-fluxy-chat@latest my-deal --example deal-room
35
+ npx @fluxy-chat/create-fluxy-chat@latest my-fleet --example fleet-panel
36
+ npx @fluxy-chat/create-fluxy-chat@latest my-game --example game-tick
37
+ npx @fluxy-chat/create-fluxy-chat@latest my-stage --example voice-stage
38
+ npx @fluxy-chat/create-fluxy-chat@latest my-comments --example comments-board
29
39
  npx @fluxy-chat/create-fluxy-chat@latest my-chat --template react -y
30
40
  npx @fluxy-chat/create-fluxy-chat@latest my-bot --adapter slack --pm pnpm
31
41
  ```
@@ -41,6 +51,7 @@ npx @fluxy-chat/create-fluxy-chat@latest my-bot --adapter slack --pm pnpm
41
51
  | `--yes` | `-y` | Skip prompts and accept defaults |
42
52
  | `--full` | | Full stack template: chat + `@assistant` + `pnpm setup` / `pnpm dev` |
43
53
  | `--minimal` | | Chat-only widget (`@fluxy-chat/ui-kit`), no platform modules |
54
+ | `--example <name>` | | Gallery: `live-cursors`, `live-cursors-chat`, `javascript-live-cursors`, `tiptap-room`, `war-room`, `iot-panel`, `draw`, `deal-room`, `fleet-panel`, `game-tick`, `voice-stage`, `comments-board`, `whiteboard` |
44
55
  | `--skip-install` | | Skip dependency installation |
45
56
  | `--no-git` | | Skip git repository initialization |
46
57
  | `--help` | `-h` | Show help |
@@ -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
  },
14
14
  "devDependencies": {
15
15
  "@cloudflare/workers-types": "^4.0.0",
@@ -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,9 @@
1
+ # FluxyChat comments board
2
+
3
+ Contextual `useThreads` pins — **not** chat `parentId` replies. Click the canvas to open `FloatingComposer`.
4
+
5
+ ```bash
6
+ npx @fluxy-chat/create-fluxy-chat@latest my-comments --example comments-board
7
+ cp .env.example .env
8
+ npm run dev
9
+ ```
@@ -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 comments</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,26 @@
1
+ {
2
+ "name": "fluxy-comments-board",
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
+ "@fluxy-chat/ui": "^0.1.4",
15
+ "react": "^19.0.0",
16
+ "react-dom": "^19.0.0",
17
+ "zustand": "^5.0.0"
18
+ },
19
+ "devDependencies": {
20
+ "@types/react": "^19.0.0",
21
+ "@types/react-dom": "^19.0.0",
22
+ "@vitejs/plugin-react": "^4.3.0",
23
+ "typescript": "^5.6.0",
24
+ "vite": "^6.0.0"
25
+ }
26
+ }
@@ -0,0 +1,86 @@
1
+ import { useState } from "react";
2
+ import { FluxyRealtimeProvider, useChat, useThreads } from "@fluxy-chat/react";
3
+ import { CommentPin, FloatingComposer, Thread } from "@fluxy-chat/ui";
4
+ import { useFluxySession, workerUrl } from "./session";
5
+
6
+ function CommentsBoard({ roomId }: { roomId: string }) {
7
+ const { threads, createThread, createComment, markThreadAsResolved, reload } = useThreads({ roomId });
8
+ const [draftPin, setDraftPin] = useState<{ x: number; y: number } | null>(null);
9
+ const [openId, setOpenId] = useState<string | null>(null);
10
+ useChat({
11
+ roomId,
12
+ replay: "off",
13
+ onServerEvent: (ev) => {
14
+ if (ev.name.startsWith("comment.")) void reload();
15
+ },
16
+ });
17
+
18
+ const openThread = threads.find((t) => t.id === openId) ?? null;
19
+
20
+ return (
21
+ <section className="panel">
22
+ <header className="chat-header">
23
+ <strong>Comments · {roomId}</strong>
24
+ <span className="status">pins + threads, not chat replies</span>
25
+ </header>
26
+ <div
27
+ className="canvas"
28
+ onClick={(event) => {
29
+ const rect = event.currentTarget.getBoundingClientRect();
30
+ setDraftPin({ x: event.clientX - rect.left, y: event.clientY - rect.top });
31
+ }}
32
+ >
33
+ {threads.map((thread) =>
34
+ thread.metadata.x != null && thread.metadata.y != null ? (
35
+ <CommentPin
36
+ key={thread.id}
37
+ x={thread.metadata.x}
38
+ y={thread.metadata.y}
39
+ count={thread.comments.length}
40
+ resolved={thread.resolved}
41
+ onClick={() => setOpenId(thread.id)}
42
+ />
43
+ ) : null,
44
+ )}
45
+ {draftPin ? (
46
+ <FloatingComposer
47
+ x={draftPin.x}
48
+ y={draftPin.y}
49
+ onCancel={() => setDraftPin(null)}
50
+ onSubmit={async (body) => {
51
+ const created = await createThread({ body, metadata: { x: draftPin.x, y: draftPin.y } });
52
+ setDraftPin(null);
53
+ if (created) setOpenId(created.id);
54
+ }}
55
+ />
56
+ ) : null}
57
+ </div>
58
+ {openThread ? (
59
+ <div style={{ padding: 12 }}>
60
+ <Thread
61
+ thread={openThread}
62
+ onReply={(body) => createComment(openThread.id, body)}
63
+ onResolve={(resolved) => markThreadAsResolved(openThread.id, resolved)}
64
+ />
65
+ </div>
66
+ ) : null}
67
+ </section>
68
+ );
69
+ }
70
+
71
+ export function App() {
72
+ const { session, loading, error } = useFluxySession();
73
+ if (!workerUrl) return <div className="shell">Set <code>VITE_FLUXYCHAT_WORKER_URL</code>.</div>;
74
+ if (loading) return <div className="shell">Starting…</div>;
75
+ if (error) return <div className="shell error">{error}</div>;
76
+ if (!session) return <div className="shell">Set a member JWT or public room id.</div>;
77
+
78
+ return (
79
+ <div className="shell">
80
+ <p className="mode-badge">{session.mode} · click the canvas to pin a thread</p>
81
+ <FluxyRealtimeProvider workerUrl={session.workerUrl} authTokenProvider={session.token} userId={session.userId}>
82
+ <CommentsBoard roomId={session.roomId} />
83
+ </FluxyRealtimeProvider>
84
+ </div>
85
+ );
86
+ }
@@ -0,0 +1,75 @@
1
+ :root {
2
+ font-family: system-ui, -apple-system, sans-serif;
3
+ line-height: 1.5;
4
+ color: #0f172a;
5
+ background: #f8fafc;
6
+ }
7
+ * { box-sizing: border-box; }
8
+ body { margin: 0; }
9
+ .shell { max-width: 720px; margin: 0 auto; padding: 1.5rem; }
10
+ .panel {
11
+ border: 1px solid #e2e8f0;
12
+ border-radius: 12px;
13
+ overflow: hidden;
14
+ background: #fff;
15
+ }
16
+ .chat-header {
17
+ display: flex;
18
+ justify-content: space-between;
19
+ padding: 0.75rem 1rem;
20
+ background: #0f172a;
21
+ color: #fff;
22
+ }
23
+ .status { font-size: 0.75rem; opacity: 0.9; }
24
+ .roster {
25
+ margin: 0;
26
+ padding: 0.5rem 1rem;
27
+ list-style: none;
28
+ font-size: 0.8rem;
29
+ color: #64748b;
30
+ border-bottom: 1px solid #e2e8f0;
31
+ }
32
+ .log { min-height: 240px; max-height: 50vh; overflow: auto; padding: 1rem; }
33
+ .composer { display: flex; gap: 0.5rem; padding: 0.75rem; border-top: 1px solid #e2e8f0; }
34
+ .composer input { flex: 1; padding: 0.5rem 0.75rem; border: 1px solid #cbd5e1; border-radius: 8px; }
35
+ .composer button, button.primary {
36
+ padding: 0.5rem 0.75rem;
37
+ border: 0;
38
+ border-radius: 8px;
39
+ background: #2563eb;
40
+ color: #fff;
41
+ cursor: pointer;
42
+ }
43
+ .mode-badge { font-size: 0.75rem; color: #64748b; margin-bottom: 0.75rem; }
44
+ .error { color: #b91c1c; }
45
+ code { font-size: 0.85em; }
46
+ .hint { font-size: 0.8rem; color: #475569; white-space: pre-wrap; background: #f1f5f9; padding: 0.75rem; border-radius: 8px; }
47
+ .readings { margin: 0; padding: 0.75rem 1rem; list-style: none; font-size: 0.875rem; }
48
+ .canvas {
49
+ position: relative;
50
+ height: min(70vh, 480px);
51
+ background: #f8fafc;
52
+ touch-action: none;
53
+ }
54
+ .dot {
55
+ position: absolute;
56
+ width: 10px;
57
+ height: 10px;
58
+ border-radius: 999px;
59
+ transform: translate(-50%, -50%);
60
+ pointer-events: none;
61
+ }
62
+ .peer-cursor {
63
+ position: absolute;
64
+ pointer-events: none;
65
+ z-index: 2;
66
+ }
67
+ .peer-cursor span {
68
+ display: block;
69
+ margin-top: 2px;
70
+ padding: 1px 6px;
71
+ border-radius: 4px;
72
+ background: currentColor;
73
+ color: #fff;
74
+ font-size: 11px;
75
+ }
@@ -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,73 @@
1
+ import { useEffect, useState } from "react";
2
+ import { FluxyChatClient } from "@fluxy-chat/sdk";
3
+
4
+ const workerUrl = import.meta.env.VITE_FLUXYCHAT_WORKER_URL?.trim();
5
+ const memberJwt = import.meta.env.VITE_FLUXYCHAT_MEMBER_JWT?.trim();
6
+ const publicRoomId = import.meta.env.VITE_FLUXYCHAT_PUBLIC_ROOM_ID?.trim();
7
+ const configuredRoomId = import.meta.env.VITE_FLUXYCHAT_ROOM_ID?.trim() || "demo";
8
+
9
+ export interface FluxySession {
10
+ workerUrl: string;
11
+ token: string;
12
+ userId: string;
13
+ roomId: string;
14
+ mode: "member" | "guest";
15
+ }
16
+
17
+ export function useFluxySession(): {
18
+ session: FluxySession | null;
19
+ loading: boolean;
20
+ error: string | null;
21
+ } {
22
+ const [session, setSession] = useState<FluxySession | null>(null);
23
+ const [loading, setLoading] = useState(Boolean(workerUrl));
24
+ const [error, setError] = useState<string | null>(null);
25
+
26
+ useEffect(() => {
27
+ if (!workerUrl) {
28
+ setLoading(false);
29
+ return;
30
+ }
31
+ if (memberJwt) {
32
+ setSession({
33
+ workerUrl,
34
+ token: memberJwt,
35
+ userId: "demo-user",
36
+ roomId: configuredRoomId,
37
+ mode: "member",
38
+ });
39
+ setLoading(false);
40
+ return;
41
+ }
42
+ if (publicRoomId) {
43
+ let cancelled = false;
44
+ void FluxyChatClient.joinPublicRoomAsGuest(workerUrl, publicRoomId, {
45
+ displayName: "Guest",
46
+ })
47
+ .then((guest) => {
48
+ if (cancelled) return;
49
+ setSession({
50
+ workerUrl,
51
+ token: guest.token,
52
+ userId: guest.userId,
53
+ roomId: guest.roomId,
54
+ mode: "guest",
55
+ });
56
+ })
57
+ .catch((err) => {
58
+ if (!cancelled) setError(err instanceof Error ? err.message : "Guest session failed");
59
+ })
60
+ .finally(() => {
61
+ if (!cancelled) setLoading(false);
62
+ });
63
+ return () => {
64
+ cancelled = true;
65
+ };
66
+ }
67
+ setLoading(false);
68
+ }, []);
69
+
70
+ return { session, loading, error };
71
+ }
72
+
73
+ export { workerUrl };
@@ -0,0 +1,13 @@
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
+ readonly VITE_FLUXYCHAT_AGENT_ID?: string;
9
+ }
10
+
11
+ interface ImportMeta {
12
+ readonly env: ImportMetaEnv;
13
+ }
@@ -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 deal room
2
+
3
+ Chat + quorum decisions (`createDecision` / `ackDecision`) + Markdown export. Open **two tabs** and ack from both.
4
+
5
+ Cross-org room linking is **server-side** REST (`/cross-org/...`), not MQTT and not this Vite app.
6
+
7
+ ```bash
8
+ npx @fluxy-chat/create-fluxy-chat@latest my-deal --example deal-room
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 deal 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,25 @@
1
+ {
2
+ "name": "fluxy-deal-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
+ "react": "^19.0.0",
15
+ "react-dom": "^19.0.0",
16
+ "zustand": "^5.0.0"
17
+ },
18
+ "devDependencies": {
19
+ "@types/react": "^19.0.0",
20
+ "@types/react-dom": "^19.0.0",
21
+ "@vitejs/plugin-react": "^4.3.0",
22
+ "typescript": "^5.6.0",
23
+ "vite": "^6.0.0"
24
+ }
25
+ }