fluxy-bot 0.8.9 → 0.9.0

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 (106) hide show
  1. package/README.md +53 -84
  2. package/bin/cli.js +1717 -6
  3. package/components.json +18 -18
  4. package/package.json +98 -109
  5. package/postcss.config.js +3 -3
  6. package/scripts/install.ps1 +1 -1
  7. package/scripts/install.sh +1 -1
  8. package/scripts/postinstall.js +47 -58
  9. package/shared/ai.ts +118 -202
  10. package/shared/config.ts +37 -39
  11. package/shared/logger.ts +6 -14
  12. package/shared/paths.ts +10 -16
  13. package/shared/relay.ts +89 -98
  14. package/supervisor/backend.ts +77 -89
  15. package/supervisor/chat/ARCHITECTURE.md +13 -14
  16. package/supervisor/chat/OnboardWizard.tsx +2194 -3419
  17. package/supervisor/chat/fluxy-main.tsx +495 -633
  18. package/supervisor/chat/fluxy.html +24 -36
  19. package/supervisor/chat/onboard-main.tsx +8 -8
  20. package/supervisor/chat/onboard.html +10 -13
  21. package/supervisor/chat/src/components/Chat/AudioBubble.tsx +94 -108
  22. package/supervisor/chat/src/components/Chat/ChatView.tsx +14 -36
  23. package/supervisor/chat/src/components/Chat/ImageLightbox.tsx +71 -89
  24. package/supervisor/chat/src/components/Chat/InputBar.tsx +498 -728
  25. package/supervisor/chat/src/components/Chat/MessageBubble.tsx +154 -246
  26. package/supervisor/chat/src/components/Chat/MessageList.tsx +114 -138
  27. package/supervisor/chat/src/components/Chat/TypingIndicator.tsx +38 -50
  28. package/supervisor/chat/src/components/LoginScreen.tsx +245 -296
  29. package/supervisor/chat/src/hooks/useChat.ts +210 -259
  30. package/supervisor/chat/src/hooks/useFluxyChat.ts +232 -322
  31. package/supervisor/chat/src/hooks/useSpeechRecognition.ts +126 -148
  32. package/supervisor/chat/src/lib/auth.ts +20 -23
  33. package/supervisor/chat/src/lib/ws-client.ts +113 -125
  34. package/supervisor/chat/src/styles/globals.css +76 -85
  35. package/supervisor/file-saver.ts +39 -52
  36. package/supervisor/fluxy-agent.ts +213 -292
  37. package/supervisor/index.ts +895 -1263
  38. package/supervisor/scheduler.ts +237 -299
  39. package/supervisor/tunnel.ts +113 -167
  40. package/supervisor/vite-dev.ts +51 -61
  41. package/supervisor/widget.js +135 -147
  42. package/supervisor/worker.ts +45 -49
  43. package/tsconfig.json +18 -24
  44. package/vite.config.ts +48 -55
  45. package/vite.fluxy.config.ts +34 -34
  46. package/worker/claude-auth.ts +202 -265
  47. package/worker/codex-auth.ts +144 -181
  48. package/worker/db.ts +63 -191
  49. package/worker/index.ts +601 -842
  50. package/workspace/MYSELF.md +1 -1
  51. package/workspace/PULSE.json +6 -6
  52. package/workspace/backend/index.ts +16 -19
  53. package/workspace/client/index.html +47 -60
  54. package/workspace/client/public/manifest.json +21 -21
  55. package/workspace/client/public/sw.js +29 -31
  56. package/workspace/client/src/App.tsx +91 -136
  57. package/workspace/client/src/components/Dashboard/DashboardPage.tsx +68 -72
  58. package/workspace/client/src/components/ErrorBoundary.tsx +11 -11
  59. package/workspace/client/src/components/Layout/DashboardLayout.tsx +35 -35
  60. package/workspace/client/src/components/Layout/Footer.tsx +12 -12
  61. package/workspace/client/src/components/Layout/MobileNav.tsx +23 -23
  62. package/workspace/client/src/components/Layout/Sidebar.tsx +81 -81
  63. package/workspace/client/src/components/ui/avatar.tsx +85 -85
  64. package/workspace/client/src/components/ui/badge.tsx +40 -41
  65. package/workspace/client/src/components/ui/button.tsx +56 -57
  66. package/workspace/client/src/components/ui/card.tsx +75 -75
  67. package/workspace/client/src/components/ui/dialog.tsx +114 -117
  68. package/workspace/client/src/components/ui/dropdown-menu.tsx +191 -199
  69. package/workspace/client/src/components/ui/input.tsx +17 -17
  70. package/workspace/client/src/components/ui/scroll-area.tsx +47 -47
  71. package/workspace/client/src/components/ui/select.tsx +145 -151
  72. package/workspace/client/src/components/ui/separator.tsx +21 -21
  73. package/workspace/client/src/components/ui/sheet.tsx +102 -102
  74. package/workspace/client/src/components/ui/skeleton.tsx +10 -10
  75. package/workspace/client/src/components/ui/switch.tsx +26 -26
  76. package/workspace/client/src/components/ui/tabs.tsx +69 -69
  77. package/workspace/client/src/components/ui/textarea.tsx +14 -14
  78. package/workspace/client/src/components/ui/tooltip.tsx +37 -37
  79. package/workspace/client/src/lib/utils.ts +1 -1
  80. package/workspace/client/src/main.tsx +3 -3
  81. package/workspace/client/src/styles/globals.css +76 -85
  82. package/workspace/skills/code-reviewer/.claude-plugin/plugin.json +3 -3
  83. package/workspace/skills/code-reviewer/skills/code-reviewer/SKILL.md +0 -6
  84. package/workspace/skills/daily-standup/.claude-plugin/plugin.json +3 -3
  85. package/workspace/skills/daily-standup/skills/daily-standup/SKILL.md +0 -7
  86. package/workspace/skills/workspace-helper/.claude-plugin/plugin.json +3 -3
  87. package/workspace/skills/workspace-helper/skills/workspace-helper/SKILL.md +0 -2
  88. package/bin/cli.backup.js +0 -2138
  89. package/cli/commands/daemon.ts +0 -42
  90. package/cli/commands/init.ts +0 -32
  91. package/cli/commands/start.ts +0 -113
  92. package/cli/commands/tunnel.temp.ts +0 -206
  93. package/cli/commands/tunnel.ts +0 -227
  94. package/cli/commands/update.ts +0 -163
  95. package/cli/core/base-adapter.ts +0 -156
  96. package/cli/core/cloudflared.ts +0 -113
  97. package/cli/core/config.ts +0 -73
  98. package/cli/core/os-detector.ts +0 -43
  99. package/cli/core/server.ts +0 -109
  100. package/cli/core/types.ts +0 -15
  101. package/cli/index.ts +0 -72
  102. package/cli/platforms/darwin.ts +0 -146
  103. package/cli/platforms/index.ts +0 -21
  104. package/cli/platforms/linux.ts +0 -156
  105. package/cli/platforms/win32.ts +0 -34
  106. package/cli/utils/ui.ts +0 -37
@@ -17,4 +17,4 @@ My human might be non-technical. I match their level.
17
17
 
18
18
  _Everything else I need to know — routing, architecture, coding rules, memory system, personality — is in my system prompt. This file is just me._
19
19
 
20
- _This file is mine to change. As I figure out who I am and how I work best — I update it._
20
+ _This file is mine to change. As I figure out who I am and how I work best — I update it._
@@ -1,8 +1,8 @@
1
1
  {
2
- "enabled": true,
3
- "intervalMinutes": 30,
4
- "quietHours": {
5
- "start": "23:00",
6
- "end": "07:00"
7
- }
2
+ "enabled": true,
3
+ "intervalMinutes": 30,
4
+ "quietHours": {
5
+ "start": "23:00",
6
+ "end": "07:00"
7
+ }
8
8
  }
@@ -9,18 +9,15 @@ const WORKSPACE = path.resolve(import.meta.dirname, '..');
9
9
  // Load workspace/.env manually (no dotenv dep needed)
10
10
  const envPath = path.join(WORKSPACE, '.env');
11
11
  if (fs.existsSync(envPath)) {
12
- for (const line of fs.readFileSync(envPath, 'utf-8').split('\n')) {
13
- const trimmed = line.trim();
14
- if (!trimmed || trimmed.startsWith('#')) continue;
15
- const eq = trimmed.indexOf('=');
16
- if (eq === -1) continue;
17
- const key = trimmed.slice(0, eq).trim();
18
- const val = trimmed
19
- .slice(eq + 1)
20
- .trim()
21
- .replace(/^["']|["']$/g, '');
22
- if (!process.env[key]) process.env[key] = val;
23
- }
12
+ for (const line of fs.readFileSync(envPath, 'utf-8').split('\n')) {
13
+ const trimmed = line.trim();
14
+ if (!trimmed || trimmed.startsWith('#')) continue;
15
+ const eq = trimmed.indexOf('=');
16
+ if (eq === -1) continue;
17
+ const key = trimmed.slice(0, eq).trim();
18
+ const val = trimmed.slice(eq + 1).trim().replace(/^["']|["']$/g, '');
19
+ if (!process.env[key]) process.env[key] = val;
20
+ }
24
21
  }
25
22
 
26
23
  // Open SQLite database
@@ -32,24 +29,24 @@ app.use(express.json());
32
29
 
33
30
  // Health check
34
31
  app.get('/health', (_req, res) => {
35
- res.json({ status: 'ok' });
32
+ res.json({ status: 'ok' });
36
33
  });
37
34
 
38
35
  // 404 catch-all
39
36
  app.use((_req, res) => {
40
- res.status(404).json({ error: 'Not found' });
37
+ res.status(404).json({ error: 'Not found' });
41
38
  });
42
39
 
43
40
  const server = app.listen(PORT, () => {
44
- console.log(`[backend] Listening on port ${PORT}`);
41
+ console.log(`[backend] Listening on port ${PORT}`);
45
42
  });
46
43
 
47
44
  server.on('error', (err: NodeJS.ErrnoException) => {
48
- console.error(`[backend] Server error: ${err.message}`);
49
- process.exit(1);
45
+ console.error(`[backend] Server error: ${err.message}`);
46
+ process.exit(1);
50
47
  });
51
48
 
52
49
  // Keep process alive — prevent exit on unhandled rejection
53
- process.on('unhandledRejection', err => {
54
- console.error('[backend] Unhandled rejection:', err);
50
+ process.on('unhandledRejection', (err) => {
51
+ console.error('[backend] Unhandled rejection:', err);
55
52
  });
@@ -1,62 +1,49 @@
1
- <!doctype html>
1
+ <!DOCTYPE html>
2
2
  <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta
6
- name="viewport"
7
- content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, interactive-widget=resizes-content"
8
- />
9
- <meta name="theme-color" content="#212121" />
10
- <meta name="apple-mobile-web-app-capable" content="yes" />
11
- <meta
12
- name="apple-mobile-web-app-status-bar-style"
13
- content="black-translucent"
14
- />
15
- <meta name="apple-mobile-web-app-title" content="Fluxy" />
16
- <link rel="apple-touch-icon" href="/fluxy-icon-192.png" />
17
- <link rel="manifest" href="/manifest.json" />
18
- <title>Fluxy</title>
19
- </head>
20
- <body class="bg-background text-foreground">
21
- <div id="root"></div>
22
- <script>
23
- // Global error handler — catches errors outside React's Error Boundary
24
- // (e.g., Vite compilation errors, module loading failures)
25
- window.addEventListener('error', function (e) {
26
- // Only show if root is empty (React didn't mount or crashed before mounting)
27
- var root = document.getElementById('root');
28
- if (root && root.children.length === 0) {
29
- root.innerHTML =
30
- '<div style="background:#222122;color:#fff;display:flex;flex-direction:column;align-items:center;justify-content:center;height:100dvh;width:100vw;position:fixed;inset:0;z-index:50;font-family:system-ui,-apple-system,sans-serif;text-align:center;padding:24px">' +
31
- '<img src="/fluxy_frame1.png" style="height:120px;width:120px;border-radius:50%;object-fit:cover;margin-bottom:32px" alt="Fluxy" />' +
32
- '<h1 style="font-size:20px;font-weight:600;margin-bottom:8px">Your app crashed</h1>' +
33
- '<p style="font-size:14px;color:rgba(255,255,255,0.5);max-width:320px;line-height:1.5">Ask the agent to fix it using the chat.</p>' +
34
- '</div>';
35
- }
36
- });
37
- </script>
38
- <script type="module" src="/src/main.tsx"></script>
39
- <script>
40
- if ('serviceWorker' in navigator) {
41
- navigator.serviceWorker.register('/sw.js').then(function (r) {
42
- r.update();
43
- if (r.waiting) {
44
- r.waiting.postMessage({ type: 'SKIP_WAITING' });
45
- }
46
- r.addEventListener('updatefound', function () {
47
- var w = r.installing;
48
- if (w)
49
- w.addEventListener('statechange', function () {
50
- if (
51
- w.state === 'installed' &&
52
- navigator.serviceWorker.controller
53
- )
54
- w.postMessage({ type: 'SKIP_WAITING' });
55
- });
56
- });
57
- });
58
- }
59
- </script>
60
- <script src="/fluxy/widget.js"></script>
61
- </body>
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, interactive-widget=resizes-content" />
6
+ <meta name="theme-color" content="#212121" />
7
+ <meta name="apple-mobile-web-app-capable" content="yes" />
8
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
9
+ <meta name="apple-mobile-web-app-title" content="Fluxy" />
10
+ <link rel="apple-touch-icon" href="/fluxy-icon-192.png" />
11
+ <link rel="manifest" href="/manifest.json" />
12
+ <title>Fluxy</title>
13
+ </head>
14
+ <body class="bg-background text-foreground">
15
+ <div id="root"></div>
16
+ <script>
17
+ // Global error handler — catches errors outside React's Error Boundary
18
+ // (e.g., Vite compilation errors, module loading failures)
19
+ window.addEventListener('error', function (e) {
20
+ // Only show if root is empty (React didn't mount or crashed before mounting)
21
+ var root = document.getElementById('root');
22
+ if (root && root.children.length === 0) {
23
+ root.innerHTML =
24
+ '<div style="background:#222122;color:#fff;display:flex;flex-direction:column;align-items:center;justify-content:center;height:100dvh;width:100vw;position:fixed;inset:0;z-index:50;font-family:system-ui,-apple-system,sans-serif;text-align:center;padding:24px">' +
25
+ '<img src="/fluxy_frame1.png" style="height:120px;width:120px;border-radius:50%;object-fit:cover;margin-bottom:32px" alt="Fluxy" />' +
26
+ '<h1 style="font-size:20px;font-weight:600;margin-bottom:8px">Your app crashed</h1>' +
27
+ '<p style="font-size:14px;color:rgba(255,255,255,0.5);max-width:320px;line-height:1.5">Ask the agent to fix it using the chat.</p>' +
28
+ '</div>';
29
+ }
30
+ });
31
+ </script>
32
+ <script type="module" src="/src/main.tsx"></script>
33
+ <script>
34
+ if('serviceWorker' in navigator){
35
+ navigator.serviceWorker.register('/sw.js').then(function(r){
36
+ r.update();
37
+ if(r.waiting){r.waiting.postMessage({type:'SKIP_WAITING'})}
38
+ r.addEventListener('updatefound',function(){
39
+ var w=r.installing;
40
+ if(w)w.addEventListener('statechange',function(){
41
+ if(w.state==='installed'&&navigator.serviceWorker.controller)w.postMessage({type:'SKIP_WAITING'});
42
+ });
43
+ });
44
+ });
45
+ }
46
+ </script>
47
+ <script src="/fluxy/widget.js"></script>
48
+ </body>
62
49
  </html>
@@ -1,23 +1,23 @@
1
1
  {
2
- "name": "Fluxy",
3
- "short_name": "Fluxy",
4
- "description": "Your AI assistant",
5
- "start_url": "/",
6
- "display": "standalone",
7
- "background_color": "#212121",
8
- "theme_color": "#212121",
9
- "icons": [
10
- {
11
- "src": "/fluxy-icon-192.png",
12
- "sizes": "192x192",
13
- "type": "image/png",
14
- "purpose": "any maskable"
15
- },
16
- {
17
- "src": "/fluxy-icon-512.png",
18
- "sizes": "512x512",
19
- "type": "image/png",
20
- "purpose": "any maskable"
21
- }
22
- ]
2
+ "name": "Fluxy",
3
+ "short_name": "Fluxy",
4
+ "description": "Your AI assistant",
5
+ "start_url": "/",
6
+ "display": "standalone",
7
+ "background_color": "#212121",
8
+ "theme_color": "#212121",
9
+ "icons": [
10
+ {
11
+ "src": "/fluxy-icon-192.png",
12
+ "sizes": "192x192",
13
+ "type": "image/png",
14
+ "purpose": "any maskable"
15
+ },
16
+ {
17
+ "src": "/fluxy-icon-512.png",
18
+ "sizes": "512x512",
19
+ "type": "image/png",
20
+ "purpose": "any maskable"
21
+ }
22
+ ]
23
23
  }
@@ -1,42 +1,40 @@
1
1
  // Service worker — PWA installability + push notifications
2
2
  self.addEventListener('install', () => self.skipWaiting());
3
- self.addEventListener('activate', e => e.waitUntil(self.clients.claim()));
3
+ self.addEventListener('activate', (e) => e.waitUntil(self.clients.claim()));
4
4
  self.addEventListener('fetch', () => {});
5
5
 
6
6
  // Push notification
7
- self.addEventListener('push', event => {
8
- let data = { title: 'Fluxy', body: 'New message' };
9
- try {
10
- data = event.data.json();
11
- } catch {}
7
+ self.addEventListener('push', (event) => {
8
+ let data = { title: 'Fluxy', body: 'New message' };
9
+ try {
10
+ data = event.data.json();
11
+ } catch {}
12
12
 
13
- event.waitUntil(
14
- self.registration.showNotification(data.title || 'Fluxy', {
15
- body: data.body || '',
16
- icon: '/fluxy-icon-192.png',
17
- badge: '/fluxy-badge.png',
18
- vibrate: [100, 50, 100],
19
- tag: data.tag || 'fluxy-default',
20
- data: { url: data.url || '/' }
21
- })
22
- );
13
+ event.waitUntil(
14
+ self.registration.showNotification(data.title || 'Fluxy', {
15
+ body: data.body || '',
16
+ icon: '/fluxy-icon-192.png',
17
+ badge: '/fluxy-badge.png',
18
+ vibrate: [100, 50, 100],
19
+ tag: data.tag || 'fluxy-default',
20
+ data: { url: data.url || '/' },
21
+ })
22
+ );
23
23
  });
24
24
 
25
25
  // Notification click — focus or open app
26
- self.addEventListener('notificationclick', event => {
27
- event.notification.close();
28
- const url = event.notification.data?.url || '/';
26
+ self.addEventListener('notificationclick', (event) => {
27
+ event.notification.close();
28
+ const url = event.notification.data?.url || '/';
29
29
 
30
- event.waitUntil(
31
- self.clients
32
- .matchAll({ type: 'window', includeUncontrolled: true })
33
- .then(clients => {
34
- for (const client of clients) {
35
- if (client.url.includes('/fluxy') && 'focus' in client) {
36
- return client.focus();
37
- }
38
- }
39
- return self.clients.openWindow(url);
40
- })
41
- );
30
+ event.waitUntil(
31
+ self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clients) => {
32
+ for (const client of clients) {
33
+ if (client.url.includes('/fluxy') && 'focus' in client) {
34
+ return client.focus();
35
+ }
36
+ }
37
+ return self.clients.openWindow(url);
38
+ })
39
+ );
42
40
  });
@@ -4,148 +4,103 @@ import DashboardLayout from './components/Layout/DashboardLayout';
4
4
  import DashboardPage from './components/Dashboard/DashboardPage';
5
5
 
6
6
  function DashboardError() {
7
- return (
8
- <div
9
- style={{
10
- background: '#222122',
11
- color: '#fff',
12
- display: 'flex',
13
- flexDirection: 'column',
14
- alignItems: 'center',
15
- justifyContent: 'center',
16
- height: '100dvh',
17
- width: '100vw',
18
- position: 'fixed',
19
- inset: 0,
20
- zIndex: 50,
21
- fontFamily: 'system-ui, -apple-system, sans-serif',
22
- textAlign: 'center',
23
- padding: '24px'
24
- }}
25
- >
26
- <video
27
- src="/fluxy_say_hi.webm"
28
- autoPlay
29
- loop
30
- muted
31
- playsInline
32
- style={{
33
- height: 120,
34
- width: 120,
35
- borderRadius: '50%',
36
- objectFit: 'cover',
37
- marginBottom: 32
38
- }}
39
- />
40
- <h1 style={{ fontSize: 20, fontWeight: 600, marginBottom: 8 }}>
41
- Your app crashed
42
- </h1>
43
- <p
44
- style={{
45
- fontSize: 14,
46
- color: 'rgba(255,255,255,0.5)',
47
- maxWidth: 320,
48
- lineHeight: 1.5
49
- }}
50
- >
51
- Ask the agent to fix it using the chat.
52
- </p>
53
- </div>
54
- );
7
+ return (
8
+ <div style={{ background: '#222122', color: '#fff', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100dvh', width: '100vw', position: 'fixed', inset: 0, zIndex: 50, fontFamily: 'system-ui, -apple-system, sans-serif', textAlign: 'center', padding: '24px' }}>
9
+ <video
10
+ src="/fluxy_say_hi.webm"
11
+ autoPlay
12
+ loop
13
+ muted
14
+ playsInline
15
+ style={{ height: 120, width: 120, borderRadius: '50%', objectFit: 'cover', marginBottom: 32 }}
16
+ />
17
+ <h1 style={{ fontSize: 20, fontWeight: 600, marginBottom: 8 }}>Your app crashed</h1>
18
+ <p style={{ fontSize: 14, color: 'rgba(255,255,255,0.5)', maxWidth: 320, lineHeight: 1.5 }}>
19
+ Ask the agent to fix it using the chat.
20
+ </p>
21
+ </div>
22
+ );
55
23
  }
56
24
 
57
25
  export default function App() {
58
- const [showOnboard, setShowOnboard] = useState(false);
59
- const [rebuildState, setRebuildState] = useState<
60
- 'idle' | 'rebuilding' | 'error'
61
- >('idle');
62
- const [buildError, setBuildError] = useState('');
26
+ const [showOnboard, setShowOnboard] = useState(false);
27
+ const [rebuildState, setRebuildState] = useState<'idle' | 'rebuilding' | 'error'>('idle');
28
+ const [buildError, setBuildError] = useState('');
63
29
 
64
- useEffect(() => {
65
- fetch('/api/settings')
66
- .then(r => r.json())
67
- .then(s => {
68
- if (s.onboard_complete !== 'true') setShowOnboard(true);
69
- })
70
- .catch(() => {});
71
- }, []);
30
+ useEffect(() => {
31
+ fetch('/api/settings')
32
+ .then((r) => r.json())
33
+ .then((s) => {
34
+ if (s.onboard_complete !== 'true') setShowOnboard(true);
35
+ })
36
+ .catch(() => {});
37
+ }, []);
72
38
 
73
- // Listen for rebuild events from Fluxy iframe via postMessage
74
- useEffect(() => {
75
- let safetyTimer: ReturnType<typeof setTimeout>;
76
- const handler = (e: MessageEvent) => {
77
- if (e.data?.type === 'fluxy:rebuilding') {
78
- setRebuildState('rebuilding');
79
- setBuildError('');
80
- // Safety: auto-reload after 60s in case app:rebuilt message is lost
81
- clearTimeout(safetyTimer);
82
- safetyTimer = setTimeout(() => location.reload(), 60_000);
83
- } else if (e.data?.type === 'fluxy:rebuilt') {
84
- clearTimeout(safetyTimer);
85
- setRebuildState('idle');
86
- location.reload();
87
- } else if (e.data?.type === 'fluxy:build-error') {
88
- clearTimeout(safetyTimer);
89
- setRebuildState('error');
90
- setBuildError(e.data.error || 'Build failed');
91
- setTimeout(() => setRebuildState('idle'), 5000);
92
- } else if (e.data?.type === 'fluxy:onboard-complete') {
93
- setShowOnboard(false);
94
- } else if (e.data?.type === 'fluxy:hmr-update') {
95
- // Vite HMR handles hot updates natively — no manual reload needed.
96
- // Manual location.reload() here was causing unnecessary full-page refreshes
97
- // that killed the chat iframe's WebSocket connection.
98
- console.log(
99
- '[dashboard] File changed — Vite HMR will handle it'
100
- );
101
- }
102
- };
103
- window.addEventListener('message', handler);
104
- return () => {
105
- window.removeEventListener('message', handler);
106
- clearTimeout(safetyTimer);
107
- };
108
- }, []);
39
+ // Listen for rebuild events from Fluxy iframe via postMessage
40
+ useEffect(() => {
41
+ let safetyTimer: ReturnType<typeof setTimeout>;
42
+ const handler = (e: MessageEvent) => {
43
+ if (e.data?.type === 'fluxy:rebuilding') {
44
+ setRebuildState('rebuilding');
45
+ setBuildError('');
46
+ // Safety: auto-reload after 60s in case app:rebuilt message is lost
47
+ clearTimeout(safetyTimer);
48
+ safetyTimer = setTimeout(() => location.reload(), 60_000);
49
+ } else if (e.data?.type === 'fluxy:rebuilt') {
50
+ clearTimeout(safetyTimer);
51
+ setRebuildState('idle');
52
+ location.reload();
53
+ } else if (e.data?.type === 'fluxy:build-error') {
54
+ clearTimeout(safetyTimer);
55
+ setRebuildState('error');
56
+ setBuildError(e.data.error || 'Build failed');
57
+ setTimeout(() => setRebuildState('idle'), 5000);
58
+ } else if (e.data?.type === 'fluxy:onboard-complete') {
59
+ setShowOnboard(false);
60
+ } else if (e.data?.type === 'fluxy:hmr-update') {
61
+ // Vite HMR handles hot updates natively — no manual reload needed.
62
+ // Manual location.reload() here was causing unnecessary full-page refreshes
63
+ // that killed the chat iframe's WebSocket connection.
64
+ console.log('[dashboard] File changed — Vite HMR will handle it');
65
+ }
66
+ };
67
+ window.addEventListener('message', handler);
68
+ return () => {
69
+ window.removeEventListener('message', handler);
70
+ clearTimeout(safetyTimer);
71
+ };
72
+ }, []);
109
73
 
110
- return (
111
- <>
112
- <ErrorBoundary fallback={<DashboardError />}>
113
- <DashboardLayout>
114
- <DashboardPage />
115
- </DashboardLayout>
116
- </ErrorBoundary>
74
+ return (
75
+ <>
76
+ <ErrorBoundary fallback={<DashboardError />}>
77
+ <DashboardLayout>
78
+ <DashboardPage />
79
+ </DashboardLayout>
80
+ </ErrorBoundary>
117
81
 
118
- {showOnboard && (
119
- <iframe
120
- src="/fluxy/onboard.html"
121
- style={{
122
- position: 'fixed',
123
- inset: 0,
124
- width: '100vw',
125
- height: '100dvh',
126
- border: 'none',
127
- zIndex: 200
128
- }}
129
- />
130
- )}
82
+ {showOnboard && (
83
+ <iframe
84
+ src="/fluxy/onboard.html"
85
+ style={{ position: 'fixed', inset: 0, width: '100vw', height: '100dvh', border: 'none', zIndex: 200 }}
86
+ />
87
+ )}
131
88
 
132
- {rebuildState !== 'idle' && (
133
- <div className="fixed inset-0 z-[49] flex flex-col items-center justify-center bg-background/90">
134
- <video
135
- src="/fluxy_tilts.webm"
136
- autoPlay
137
- loop
138
- muted
139
- playsInline
140
- className="h-24 w-24 rounded-full object-cover"
141
- />
142
- <p className="mt-4 text-sm text-muted-foreground">
143
- {rebuildState === 'rebuilding'
144
- ? 'Rebuilding app...'
145
- : buildError}
146
- </p>
147
- </div>
148
- )}
149
- </>
150
- );
89
+ {rebuildState !== 'idle' && (
90
+ <div className="fixed inset-0 z-[49] flex flex-col items-center justify-center bg-background/90">
91
+ <video
92
+ src="/fluxy_tilts.webm"
93
+ autoPlay
94
+ loop
95
+ muted
96
+ playsInline
97
+ className="h-24 w-24 rounded-full object-cover"
98
+ />
99
+ <p className="mt-4 text-sm text-muted-foreground">
100
+ {rebuildState === 'rebuilding' ? 'Rebuilding app...' : buildError}
101
+ </p>
102
+ </div>
103
+ )}
104
+ </>
105
+ );
151
106
  }