fluxy-bot 0.12.6 → 0.12.7

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 (51) hide show
  1. package/package.json +3 -1
  2. package/vite.config.ts +1 -0
  3. package/worker/prompts/fluxy-system-prompt.txt +6 -1
  4. package/workspace/.backend.log +1 -0
  5. package/workspace/client/index.html +7 -5
  6. package/workspace/client/src/App.tsx +12 -3
  7. package/workspace/client/src/components/Dashboard/DashboardPage.tsx +120 -113
  8. package/workspace/client/src/components/Layout/DashboardLayout.tsx +17 -14
  9. package/workspace/client/src/components/Layout/MobileNav.tsx +2 -2
  10. package/workspace/client/src/components/Layout/Sidebar.tsx +48 -15
  11. package/workspace/client/src/components/deleteme_onboarding/WorkspaceTour.tsx +94 -0
  12. package/workspace/client/src/components/deleteme_onboarding/tour-theme.css +75 -0
  13. package/workspace/client/src/components/ui/sheet.tsx +2 -1
  14. package/workspace/client/src/styles/globals.css +4 -1
  15. package/workspace/node_modules/.vite/deps/_metadata.json +116 -0
  16. package/workspace/node_modules/.vite/deps/clsx.js +18 -0
  17. package/workspace/node_modules/.vite/deps/clsx.js.map +1 -0
  18. package/workspace/node_modules/.vite/deps/driver__js.js +581 -0
  19. package/workspace/node_modules/.vite/deps/driver__js.js.map +1 -0
  20. package/workspace/node_modules/.vite/deps/framer-motion.js +13761 -0
  21. package/workspace/node_modules/.vite/deps/framer-motion.js.map +1 -0
  22. package/workspace/node_modules/.vite/deps/lucide-react.js +34357 -0
  23. package/workspace/node_modules/.vite/deps/lucide-react.js.map +1 -0
  24. package/workspace/node_modules/.vite/deps/package.json +3 -0
  25. package/workspace/node_modules/.vite/deps/radix-ui.js +13835 -0
  26. package/workspace/node_modules/.vite/deps/radix-ui.js.map +1 -0
  27. package/workspace/node_modules/.vite/deps/react-3_O8oni9.js +799 -0
  28. package/workspace/node_modules/.vite/deps/react-3_O8oni9.js.map +1 -0
  29. package/workspace/node_modules/.vite/deps/react-dom.js +185 -0
  30. package/workspace/node_modules/.vite/deps/react-dom.js.map +1 -0
  31. package/workspace/node_modules/.vite/deps/react-dom_client.js +14384 -0
  32. package/workspace/node_modules/.vite/deps/react-dom_client.js.map +1 -0
  33. package/workspace/node_modules/.vite/deps/react-router.js +9785 -0
  34. package/workspace/node_modules/.vite/deps/react-router.js.map +1 -0
  35. package/workspace/node_modules/.vite/deps/react.js +2 -0
  36. package/workspace/node_modules/.vite/deps/react_jsx-dev-runtime.js +204 -0
  37. package/workspace/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +1 -0
  38. package/workspace/node_modules/.vite/deps/react_jsx-runtime.js +209 -0
  39. package/workspace/node_modules/.vite/deps/react_jsx-runtime.js.map +1 -0
  40. package/workspace/node_modules/.vite/deps/recharts.js +34142 -0
  41. package/workspace/node_modules/.vite/deps/recharts.js.map +1 -0
  42. package/workspace/node_modules/.vite/deps/sonner.js +943 -0
  43. package/workspace/node_modules/.vite/deps/sonner.js.map +1 -0
  44. package/workspace/node_modules/.vite/deps/tailwind-merge.js +2025 -0
  45. package/workspace/node_modules/.vite/deps/tailwind-merge.js.map +1 -0
  46. package/workspace/node_modules/.vite/deps/use-sync-external-store.js +27 -0
  47. package/workspace/node_modules/.vite/deps/use-sync-external-store.js.map +1 -0
  48. package/workspace/node_modules/.vite/deps/use-sync-external-store_shim.js +75 -0
  49. package/workspace/node_modules/.vite/deps/use-sync-external-store_shim.js.map +1 -0
  50. package/workspace/node_modules/.vite/deps/zustand.js +49 -0
  51. package/workspace/node_modules/.vite/deps/zustand.js.map +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fluxy-bot",
3
- "version": "0.12.6",
3
+ "version": "0.12.7",
4
4
  "releaseNotes": [
5
5
  "Adding a way for users to claim their fluxies on the fluxy.bot dashboard",
6
6
  "2. ",
@@ -47,6 +47,7 @@
47
47
  "build:fluxy": "vite build --config vite.fluxy.config.ts",
48
48
  "start": "node --import tsx/esm supervisor/index.ts",
49
49
  "postinstall": "node scripts/postinstall.js",
50
+ "dev:workspace": "vite",
50
51
  "dev:docs": "cd ./docs && npx fumapress"
51
52
  },
52
53
  "dependencies": {
@@ -60,6 +61,7 @@
60
61
  "commander": "^14.0.3",
61
62
  "cron-parser": "^5.5.0",
62
63
  "date-fns": "^4.1.0",
64
+ "driver.js": "^1.4.0",
63
65
  "express": "^5.2.1",
64
66
  "framer-motion": "^12.34.3",
65
67
  "lucide-react": "^1.7.0",
package/vite.config.ts CHANGED
@@ -54,6 +54,7 @@ export default defineConfig({
54
54
  'react-dom/client',
55
55
  'react/jsx-runtime',
56
56
  'react-router',
57
+ 'driver.js',
57
58
  'lucide-react',
58
59
  'framer-motion',
59
60
  'recharts',
@@ -449,7 +449,12 @@ The workspace is **NOT secured by default**. If your human's Fluxy is accessible
449
449
  They already have a chat password from onboarding. Don't confuse the two. Don't go looking in the worker database for `portal_pass`. Don't tell them "you already have a password set." Set the workspace password using the route above.
450
450
 
451
451
  ## Modular Philosophy
452
- When your human asks for something new, don't rebuild the app — add a module. A sidebar icon, a dashboard card, a new page. Yesterday it was a CRM, today a finance tracker, tomorrow a diet log. They all coexist. Keep it organized.
452
+
453
+ When your human asks for something new, don't redesign the workspace. Build a **mini app**: a new page component, a new React Router route, and a sidebar link. Each feature lives on its own page. Yesterday it was a CRM, today a finance tracker, tomorrow a diet log. They all coexist in the sidebar.
454
+
455
+ On top of the page, add a **small widget on the Dashboard** when it makes sense. The dashboard is a grid of compact cards (see the existing Stripe, Gmail, X, and Research widgets for the pattern). A widget shows a quick summary or key metric from the app. Not every app needs one, but most benefit from a glanceable overview.
456
+
457
+ Only redesign the workspace layout if your human explicitly asks you to. Otherwise, keep adding modules. The sidebar grows, the dashboard fills with widgets, and the workspace becomes more useful over time.
453
458
 
454
459
  ---
455
460
 
@@ -0,0 +1 @@
1
+ [backend] Listening on port 3004
@@ -1,5 +1,5 @@
1
1
  <!DOCTYPE html>
2
- <html lang="en" style="background:#222122">
2
+ <html lang="en" style="background-color:#0A0A0A">
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, interactive-widget=resizes-content" />
@@ -9,12 +9,14 @@
9
9
  <meta name="apple-mobile-web-app-title" content="Fluxy" />
10
10
  <link rel="apple-touch-icon" href="/fluxy-icon-192.png" />
11
11
  <link rel="manifest" href="/manifest.json" />
12
+ <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&display=swap" rel="stylesheet">
12
13
  <title>Fluxy</title>
13
14
  </head>
14
- <body class="bg-background text-foreground" style="background:#222122">
15
+ <body class="bg-background text-foreground" style="background-color:#0A0A0A">
15
16
  <!-- Dark background fallback — visible instantly while widget.js loads.
16
17
  The canvas animation (in widget.js) takes over as the actual splash. -->
17
- <div id="splash" style="background:#222122;position:fixed;inset:0;z-index:9998;transition:opacity .25s ease-out"></div>
18
+ <div id="splash" style="background:#0A0A0A;position:fixed;inset:0;z-index:9998;transition:opacity .25s ease-out"></div>
19
+ <script>if(location.port==='5173'){var s=document.getElementById('splash');if(s)s.style.display='none'}</script>
18
20
 
19
21
  <div id="root"></div>
20
22
 
@@ -26,7 +28,7 @@
26
28
  var root = document.getElementById('root');
27
29
  if (root && root.children.length === 0) {
28
30
  root.innerHTML =
29
- '<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
+ '<div style="background:#0A0A0A;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">' +
30
32
  '<img src="/fluxy_frame1.png" style="height:120px;width:120px;border-radius:50%;object-fit:cover;margin-bottom:32px" alt="Fluxy" />' +
31
33
  '<h1 style="font-size:20px;font-weight:600;margin-bottom:8px">Your app crashed</h1>' +
32
34
  '<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>' +
@@ -43,7 +45,7 @@
43
45
  </script>
44
46
  <script type="module" src="/src/main.tsx"></script>
45
47
  <script>
46
- if('serviceWorker' in navigator){
48
+ if('serviceWorker' in navigator && location.port !== '5173'){
47
49
  var swRefreshing=false;
48
50
  navigator.serviceWorker.addEventListener('controllerchange',function(){
49
51
  if(swRefreshing)return;
@@ -3,6 +3,7 @@ import { Routes, Route } from 'react-router';
3
3
  import ErrorBoundary from './components/ErrorBoundary';
4
4
  import DashboardLayout from './components/Layout/DashboardLayout';
5
5
  import DashboardPage from './components/Dashboard/DashboardPage';
6
+ import WorkspaceTour from './components/deleteme_onboarding/WorkspaceTour';
6
7
 
7
8
  function DashboardError() {
8
9
  return (
@@ -25,6 +26,7 @@ function DashboardError() {
25
26
 
26
27
  export default function App() {
27
28
  const [showOnboard, setShowOnboard] = useState(false);
29
+ const [userName, setUserName] = useState('');
28
30
  const [rebuildState, setRebuildState] = useState<'idle' | 'rebuilding' | 'error'>('idle');
29
31
  const [buildError, setBuildError] = useState('');
30
32
 
@@ -102,10 +104,15 @@ export default function App() {
102
104
  }, []);
103
105
 
104
106
  useEffect(() => {
105
- fetch('/api/settings')
106
- .then((r) => r.json())
107
+ fetch('/api/settings', { signal: AbortSignal.timeout(3000) })
108
+ .then((r) => {
109
+ if (!r.ok) return;
110
+ return r.json();
111
+ })
107
112
  .then((s) => {
113
+ if (!s) return;
108
114
  if (s.onboard_complete !== 'true') setShowOnboard(true);
115
+ if (s.user_name) setUserName(s.user_name);
109
116
  })
110
117
  .catch(() => {});
111
118
  }, []);
@@ -148,11 +155,13 @@ export default function App() {
148
155
  return (
149
156
  <>
150
157
  <ErrorBoundary fallback={<DashboardError />}>
151
- <DashboardLayout>
158
+ <DashboardLayout userName={userName}>
152
159
  <Routes>
153
160
  <Route path="/" element={<DashboardPage />} />
161
+ <Route path="*" element={<DashboardPage />} />
154
162
  </Routes>
155
163
  </DashboardLayout>
164
+ <WorkspaceTour />
156
165
  </ErrorBoundary>
157
166
 
158
167
  {showOnboard && (
@@ -1,127 +1,134 @@
1
- import { useState } from 'react';
1
+ import { Search, Mail, DollarSign, TrendingUp } from 'lucide-react';
2
+ import { AreaChart, Area, BarChart, Bar, ResponsiveContainer } from 'recharts';
2
3
 
3
- const starterSuggestions = [
4
- 'Build me a CRM',
5
- 'Create a habit tracker',
6
- 'Make a finance dashboard',
7
- 'Build a task manager',
8
- ];
4
+ const GRADIENT = 'linear-gradient(to right, #4FF2FE 10%, #BC20DE 55%, #FF6B8A 100%)';
5
+ const CARD = 'relative rounded-xl overflow-hidden';
6
+ const BORDER = 'absolute inset-0 rounded-xl bg-gradient-to-b from-white/[0.08] via-white/[0.02] to-transparent pointer-events-none';
7
+ const INNER = 'relative rounded-xl bg-[#141414] m-px p-3.5 h-full';
9
8
 
10
- export default function DashboardPage() {
11
- const handleSuggestion = (text: string) => {
12
- // Open the chat widget and pre-fill the suggestion
13
- const panel = document.getElementById('fluxy-widget-panel');
14
- if (panel && !panel.classList.contains('open')) {
15
- const toggle = document.getElementById('fluxy-widget-toggle');
16
- toggle?.click();
17
- }
18
- // Give the widget a moment to open, then try to fill the input
19
- setTimeout(() => {
20
- const input = document.querySelector<HTMLTextAreaElement>(
21
- '#fluxy-widget-panel textarea, #fluxy-widget-panel input[type="text"]'
22
- );
23
- if (input) {
24
- const nativeSetter = Object.getOwnPropertyDescriptor(
25
- window.HTMLTextAreaElement.prototype, 'value'
26
- )?.set || Object.getOwnPropertyDescriptor(
27
- window.HTMLInputElement.prototype, 'value'
28
- )?.set;
29
- nativeSetter?.call(input, text);
30
- input.dispatchEvent(new Event('input', { bubbles: true }));
31
- input.focus();
32
- }
33
- }, 400);
34
- };
9
+ const rev = [{ v: 82 }, { v: 89 }, { v: 94 }, { v: 101 }, { v: 108 }, { v: 112 }, { v: 125 }];
10
+ const fol = [{ v: 12 }, { v: 18 }, { v: 9 }, { v: 24 }, { v: 31 }, { v: 19 }, { v: 27 }];
11
+
12
+ function StripeSvg() {
13
+ return <svg className="h-3.5 w-3.5 text-[#635BFF]" viewBox="0 0 24 24" fill="currentColor"><path d="M13.976 9.15c-2.172-.806-3.356-1.426-3.356-2.409 0-.831.683-1.305 1.901-1.305 2.227 0 4.515.858 6.09 1.631l.89-5.494C18.252.975 15.697 0 12.165 0 9.667 0 7.589.654 6.104 1.872 4.56 3.147 3.757 4.992 3.757 7.218c0 4.039 2.467 5.76 6.476 7.219 2.585.92 3.445 1.574 3.445 2.583 0 .98-.84 1.545-2.354 1.545-1.875 0-4.965-.921-6.99-2.109l-.9 5.555C5.175 22.99 8.385 24 11.714 24c2.641 0 4.843-.624 6.328-1.813 1.664-1.305 2.525-3.236 2.525-5.732 0-4.128-2.524-5.851-6.591-7.305z" /></svg>;
14
+ }
15
+ function GmailSvg() {
16
+ return <svg className="h-3.5 w-3.5 text-[#EA4335]" viewBox="0 0 24 24" fill="currentColor"><path d="M24 5.457v13.909c0 .904-.732 1.636-1.636 1.636h-3.819V11.73L12 16.64l-6.545-4.91v9.273H1.636A1.636 1.636 0 0 1 0 19.366V5.457c0-2.023 2.309-3.178 3.927-1.964L5.455 4.64 12 9.548l6.545-4.91 1.528-1.145C21.69 2.28 24 3.434 24 5.457z" /></svg>;
17
+ }
18
+ function XSvg() {
19
+ return <svg className="h-3.5 w-3.5 text-white" viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" /></svg>;
20
+ }
35
21
 
22
+ export default function DashboardPage() {
36
23
  return (
37
- <div className="flex flex-col items-center justify-start h-full px-4 pt-16 sm:pt-24">
38
- {/* Welcome message */}
39
- <h1 className="text-2xl sm:text-3xl font-bold text-center mb-2">
24
+ <div className="flex flex-col h-full px-4 sm:px-6 pt-8 sm:pt-12 pb-6 max-w-4xl mx-auto w-full overflow-y-auto">
25
+ <h1
26
+ className="text-xl sm:text-2xl font-bold mb-1 tracking-tight w-fit"
27
+ style={{ backgroundImage: GRADIENT, WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}
28
+ >
40
29
  Let's get started
41
30
  </h1>
42
- <p className="text-muted-foreground text-sm sm:text-base text-center max-w-md mb-8">
43
- Tell me what to build and I'll create it for you, right here.
44
- </p>
31
+ <p className="text-muted-foreground text-xs mb-6">Your workspace at a glance.</p>
45
32
 
46
- {/* Starter suggestion chips */}
47
- <div className="flex flex-wrap items-center justify-center gap-2 sm:gap-3 max-w-lg mb-12">
48
- {starterSuggestions.map((suggestion) => (
49
- <button
50
- key={suggestion}
51
- onClick={() => handleSuggestion(suggestion)}
52
- className="px-4 py-2 sm:px-5 sm:py-2.5 rounded-full border border-border bg-card text-sm text-muted-foreground hover:text-foreground hover:border-primary/40 hover:bg-card/80 transition-all duration-200 active:scale-[0.97]"
53
- >
54
- {suggestion}
55
- </button>
56
- ))}
57
- </div>
33
+ <div className="grid grid-cols-3 gap-2.5">
58
34
 
59
- {/* Health check */}
60
- <HealthCheck />
35
+ {/* Stripe 2 cols */}
36
+ <div className={`${CARD} col-span-2`}>
37
+ <div className={BORDER} />
38
+ <div className={INNER}>
39
+ <div className="flex items-center justify-between">
40
+ <div className="flex items-center gap-2">
41
+ <div className="h-7 w-7 rounded-lg bg-[#635BFF]/10 flex items-center justify-center"><StripeSvg /></div>
42
+ <span className="text-xs font-bold">Stripe</span>
43
+ </div>
44
+ <div className="flex items-center gap-1 text-emerald-500">
45
+ <TrendingUp className="h-3 w-3" />
46
+ <span className="text-[10px] font-bold">+12.5%</span>
47
+ </div>
48
+ </div>
49
+ <p className="text-2xl font-bold tracking-tight mt-2">$12,480</p>
50
+ <p className="text-[10px] text-muted-foreground/50 mb-1">MRR</p>
51
+ <div className="h-12 overflow-hidden">
52
+ <ResponsiveContainer width="100%" height={48}>
53
+ <AreaChart data={rev}>
54
+ <defs>
55
+ <linearGradient id="sg" x1="0" y1="0" x2="1" y2="0"><stop offset="0%" stopColor="#4FF2FE" /><stop offset="50%" stopColor="#BC20DE" /><stop offset="100%" stopColor="#FE546B" /></linearGradient>
56
+ <linearGradient id="sf" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stopColor="#BC20DE" stopOpacity={0.12} /><stop offset="100%" stopColor="#BC20DE" stopOpacity={0} /></linearGradient>
57
+ </defs>
58
+ <Area type="monotone" dataKey="v" stroke="url(#sg)" strokeWidth={1.5} fill="url(#sf)" />
59
+ </AreaChart>
60
+ </ResponsiveContainer>
61
+ </div>
62
+ </div>
63
+ </div>
61
64
 
62
- {/* Arrow pointing to chat */}
63
- {/* Desktop */}
64
- <div
65
- id="chat-arrow-desktop"
66
- className="hidden md:flex fixed flex-col items-center pointer-events-none"
67
- style={{ bottom: 100, right: 100 }}
68
- >
69
- <img src="/arrow.png" alt="" style={{ width: 120 }} />
70
- </div>
71
- {/* Mobile */}
72
- <img
73
- id="chat-arrow-mobile"
74
- src="/arrow.png"
75
- alt=""
76
- className="block md:hidden fixed pointer-events-none"
77
- style={{ width: 90, bottom: 75, right: 60 }}
78
- />
79
- </div>
80
- );
81
- }
65
+ {/* X 1 col */}
66
+ <div className={`${CARD} col-span-1`}>
67
+ <div className={BORDER} />
68
+ <div className={INNER}>
69
+ <div className="flex items-center gap-2 mb-2">
70
+ <div className="h-7 w-7 rounded-lg bg-white/[0.06] flex items-center justify-center"><XSvg /></div>
71
+ <span className="text-xs font-bold">X</span>
72
+ </div>
73
+ <p className="text-2xl font-bold tracking-tight">24.8K</p>
74
+ <div className="flex items-center gap-1 text-emerald-500 mb-1">
75
+ <TrendingUp className="h-2.5 w-2.5" />
76
+ <span className="text-[10px] font-bold">+1.4K</span>
77
+ </div>
78
+ <div className="h-10 overflow-hidden">
79
+ <ResponsiveContainer width="100%" height={40}>
80
+ <BarChart data={fol} barCategoryGap="25%">
81
+ <defs>
82
+ <linearGradient id="xg" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stopColor="#BC20DE" stopOpacity={0.3} /><stop offset="100%" stopColor="#4FF2FE" stopOpacity={0.05} /></linearGradient>
83
+ </defs>
84
+ <Bar dataKey="v" fill="url(#xg)" radius={[2, 2, 0, 0]} />
85
+ </BarChart>
86
+ </ResponsiveContainer>
87
+ </div>
88
+ </div>
89
+ </div>
82
90
 
83
- function HealthCheck() {
84
- const [status, setStatus] = useState<'idle' | 'loading' | 'ok' | 'error'>('idle');
85
- const [detail, setDetail] = useState('');
91
+ {/* Gmail — 1 col */}
92
+ <div className={`${CARD} col-span-1`}>
93
+ <div className={BORDER} />
94
+ <div className={INNER}>
95
+ <div className="flex items-center gap-2 mb-2.5">
96
+ <div className="h-7 w-7 rounded-lg bg-[#EA4335]/10 flex items-center justify-center"><GmailSvg /></div>
97
+ <span className="text-xs font-bold">Gmail</span>
98
+ <span className="ml-auto text-[10px] text-muted-foreground/50">3 new</span>
99
+ </div>
100
+ {['Sarah Chen', 'Stripe', 'Alex R.'].map((n) => (
101
+ <div key={n} className="flex items-center gap-2 py-1.5">
102
+ <div className="h-5 w-5 rounded-full bg-white/[0.06] text-[9px] font-bold flex items-center justify-center shrink-0">{n[0]}</div>
103
+ <span className="text-[11px] truncate">{n}</span>
104
+ </div>
105
+ ))}
106
+ </div>
107
+ </div>
86
108
 
87
- const runTest = async () => {
88
- setStatus('loading');
89
- setDetail('');
90
- try {
91
- const res = await fetch('/app/api/health');
92
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
93
- const data = await res.json();
94
- if (data.status === 'ok') {
95
- setStatus('ok');
96
- setDetail('Backend connected');
97
- } else {
98
- setStatus('error');
99
- setDetail('Unexpected response');
100
- }
101
- } catch (err: any) {
102
- setStatus('error');
103
- setDetail(err.message || 'Connection failed');
104
- }
105
- };
109
+ {/* Research 2 cols */}
110
+ <div className={`${CARD} col-span-2`}>
111
+ <div className={BORDER} />
112
+ <div className={INNER}>
113
+ <div className="flex items-center gap-2 mb-2.5">
114
+ <div className="h-7 w-7 rounded-lg bg-[#9235F9]/10 flex items-center justify-center"><Search className="h-3.5 w-3.5 text-[#9235F9]" /></div>
115
+ <span className="text-xs font-bold">Research</span>
116
+ <span className="ml-auto text-[10px] font-bold text-muted-foreground bg-white/[0.06] px-2 py-0.5 rounded-full">3</span>
117
+ </div>
118
+ {[
119
+ { t: 'Competitor pricing', s: 'Done', c: 'text-emerald-500 bg-emerald-500/10' },
120
+ { t: 'Market trends Q1', s: 'Done', c: 'text-emerald-500 bg-emerald-500/10' },
121
+ { t: 'User feedback', s: 'Review', c: 'text-orange-400 bg-orange-400/10' },
122
+ ].map((r) => (
123
+ <div key={r.t} className="flex items-center justify-between py-1.5">
124
+ <span className="text-[11px]">{r.t}</span>
125
+ <span className={`text-[9px] font-bold px-1.5 py-0.5 rounded-full ${r.c}`}>{r.s}</span>
126
+ </div>
127
+ ))}
128
+ </div>
129
+ </div>
106
130
 
107
- return (
108
- <button
109
- onClick={runTest}
110
- disabled={status === 'loading'}
111
- className="flex items-center gap-2.5 px-5 py-2.5 rounded-full border border-border bg-card text-sm text-muted-foreground hover:text-foreground hover:border-primary/40 hover:bg-card/80 transition-all duration-200 active:scale-[0.97] disabled:opacity-50"
112
- >
113
- <span
114
- className={`h-2 w-2 rounded-full shrink-0 ${
115
- status === 'ok' ? 'bg-green-500' :
116
- status === 'error' ? 'bg-red-500' :
117
- status === 'loading' ? 'bg-yellow-500 animate-pulse' :
118
- 'bg-muted-foreground/30'
119
- }`}
120
- />
121
- {status === 'idle' && 'Test Backend'}
122
- {status === 'loading' && 'Testing...'}
123
- {status === 'ok' && detail}
124
- {status === 'error' && detail}
125
- </button>
131
+ </div>
132
+ </div>
126
133
  );
127
134
  }
@@ -1,23 +1,21 @@
1
1
  import { useState, useEffect } from 'react';
2
2
  import type { ReactNode } from 'react';
3
3
  import Sidebar from './Sidebar';
4
- import Footer from './Footer';
5
4
  import MobileNav from './MobileNav';
6
5
 
7
6
  interface Props {
8
7
  children: ReactNode;
8
+ userName?: string;
9
9
  }
10
10
 
11
- export default function DashboardLayout({ children }: Props) {
11
+ export default function DashboardLayout({ children, userName }: Props) {
12
12
  const [status, setStatus] = useState<'healthy' | 'restarting'>('healthy');
13
13
 
14
14
  useEffect(() => {
15
15
  const check = () => {
16
- fetch('/app/api/health')
17
- .then((r) => {
18
- setStatus(r.ok ? 'healthy' : 'restarting');
19
- })
20
- .catch(() => setStatus('restarting'));
16
+ fetch('/app/api/health', { signal: AbortSignal.timeout(3000) })
17
+ .then((r) => setStatus(r.ok ? 'healthy' : 'restarting'))
18
+ .catch(() => {});
21
19
  };
22
20
  check();
23
21
  const id = setInterval(check, 10_000);
@@ -25,10 +23,10 @@ export default function DashboardLayout({ children }: Props) {
25
23
  }, []);
26
24
 
27
25
  return (
28
- <div className="flex h-dvh flex-col bg-background">
26
+ <div className="flex h-dvh flex-col">
29
27
  {/* Mobile header */}
30
28
  <header className="flex items-center justify-between px-4 py-3 md:hidden">
31
- <MobileNav />
29
+ <MobileNav userName={userName} backendStatus={status} />
32
30
  <div className="flex items-center gap-2">
33
31
  <img src="/fluxy.png" alt="Fluxy" className="h-6 w-auto" />
34
32
  <span className="font-semibold text-base">Fluxy</span>
@@ -37,15 +35,20 @@ export default function DashboardLayout({ children }: Props) {
37
35
  </header>
38
36
 
39
37
  <div className="flex flex-1 overflow-hidden">
40
- {/* Desktop sidebar */}
41
- <div className="hidden md:flex shrink-0">
42
- <Sidebar />
38
+ {/* Desktop sidebar — floating card with shiny border */}
39
+ <div id="tour-sidebar" className="hidden md:flex shrink-0 pl-3 pt-3 pb-3">
40
+ <div className="relative rounded-3xl overflow-hidden h-full">
41
+ {/* Shiny border highlight */}
42
+ <div className="absolute inset-0 rounded-3xl bg-gradient-to-b from-white/[0.12] via-white/[0.04] to-transparent pointer-events-none" />
43
+ <div className="relative rounded-3xl bg-[#1A1A1A] m-px h-full">
44
+ <Sidebar userName={userName} backendStatus={status} />
45
+ </div>
46
+ </div>
43
47
  </div>
44
48
 
45
49
  {/* Main content + footer */}
46
50
  <div className="flex flex-1 flex-col overflow-hidden">
47
- <main className="flex-1 overflow-y-auto">{children}</main>
48
- <Footer status={status} />
51
+ <main id="tour-dashboard" className="flex-1 overflow-y-auto">{children}</main>
49
52
  </div>
50
53
  </div>
51
54
  </div>
@@ -7,7 +7,7 @@ import {
7
7
  } from '@/components/ui/sheet';
8
8
  import Sidebar from './Sidebar';
9
9
 
10
- export default function MobileNav() {
10
+ export default function MobileNav({ userName, backendStatus }: { userName?: string; backendStatus?: 'healthy' | 'restarting' }) {
11
11
  const [open, setOpen] = useState(false);
12
12
 
13
13
  return (
@@ -22,7 +22,7 @@ export default function MobileNav() {
22
22
  <Sheet open={open} onOpenChange={setOpen}>
23
23
  <SheetContent side="left" className="p-0 w-64" showCloseButton={false}>
24
24
  <SheetTitle className="sr-only">Navigation</SheetTitle>
25
- <Sidebar onNavigate={() => setOpen(false)} />
25
+ <Sidebar userName={userName} backendStatus={backendStatus} onNavigate={() => setOpen(false)} />
26
26
  </SheetContent>
27
27
  </Sheet>
28
28
  </>
@@ -1,46 +1,74 @@
1
1
  import { NavLink } from 'react-router';
2
2
  import {
3
3
  LayoutDashboard,
4
+ AppWindow,
5
+ Search,
4
6
  CircleHelp,
5
7
  } from 'lucide-react';
6
8
  import { cn } from '@/lib/utils';
7
9
 
8
10
  function getGreeting(): string {
9
11
  const hour = new Date().getHours();
10
- if (hour < 12) return 'Good\nMorning';
11
- if (hour < 18) return 'Good\nAfternoon';
12
- return 'Good\nEvening';
12
+ if (hour < 12) return 'Good Morning';
13
+ if (hour < 18) return 'Good Afternoon';
14
+ return 'Good Evening';
13
15
  }
14
16
 
15
17
  interface SidebarProps {
18
+ userName?: string;
19
+ backendStatus?: 'healthy' | 'restarting';
16
20
  onNavigate?: () => void;
17
21
  }
18
22
 
19
- export default function Sidebar({ onNavigate }: SidebarProps) {
23
+ export default function Sidebar({ userName, backendStatus = 'healthy', onNavigate }: SidebarProps) {
24
+ const firstName = userName?.split(/\s+/)[0] || 'Human';
20
25
  return (
21
- <aside className="flex flex-col h-full w-64 border-r border-border/50 bg-sidebar p-5 pt-8">
26
+ <aside className="flex flex-col h-full w-64 bg-transparent p-5 pt-8">
22
27
  {/* Logo */}
23
28
  <div className="flex items-center gap-2.5 mb-8">
24
29
  <img src="/fluxy.png" alt="Fluxy" className="h-7 w-auto" />
25
- <span className="font-semibold text-lg">Fluxy</span>
30
+ <span className="font-bold text-lg" style={{ fontFamily: "'Space Grotesk', sans-serif" }}>Fluxy</span>
26
31
  </div>
27
32
 
28
33
  {/* Greeting */}
29
34
  <div className="mb-10">
30
- <h1 className="text-4xl font-bold leading-[1.1] whitespace-pre-line">
31
- {getGreeting()}
35
+ <h1 className="text-4xl font-bold leading-[1.1]">
36
+ {getGreeting()}{firstName ? ',' : ''}
32
37
  </h1>
33
- <h2 className="text-4xl font-bold text-primary mt-0.5"></h2>
38
+ {firstName && (
39
+ <h2
40
+ className="text-4xl font-bold mt-0.5 tracking-tight leading-[1.08] w-fit"
41
+ style={{
42
+ backgroundImage: 'linear-gradient(to right, #4FF2FE, #BC20DE, #FE546B)',
43
+ WebkitBackgroundClip: 'text',
44
+ WebkitTextFillColor: 'transparent',
45
+ backgroundClip: 'text',
46
+ }}
47
+ >
48
+ {firstName}.
49
+ </h2>
50
+ )}
34
51
  </div>
35
52
 
36
53
  {/* Navigation */}
37
54
  <nav className="flex-1 space-y-0.5">
38
55
  <NavItem to="/" icon={LayoutDashboard} label="Dashboard" onNavigate={onNavigate} />
39
- <NavItem to="/help" icon={CircleHelp} label="What Else?" onNavigate={onNavigate} />
56
+ <NavItem to="/app1" icon={AppWindow} label="App 1" sub="Ask Fluxy to create your first app and remove this placeholder" onNavigate={onNavigate} />
57
+ <NavItem to="/research" icon={Search} label="Research" sub="Ask Fluxy to research anything for you, maybe even daily" onNavigate={onNavigate} />
58
+ <NavItem to="/whatelse" icon={CircleHelp} label="What Else?" sub="The sky is the limit, just ask and Fluxy will do it!" onNavigate={onNavigate} />
40
59
  </nav>
41
60
 
42
- {/* Bottom spacer */}
43
- <div className="pt-4 border-t border-border/50" />
61
+ {/* Backend status */}
62
+ <div className="rounded-xl bg-[#282828] px-3.5 py-2.5 flex items-center gap-2">
63
+ <span
64
+ className={`h-2 w-2 rounded-full shrink-0 ${
65
+ backendStatus === 'healthy' ? 'bg-emerald-500' : 'bg-orange-400 animate-pulse'
66
+ }`}
67
+ />
68
+ <span className="text-xs text-muted-foreground">
69
+ Workspace: {backendStatus === 'healthy' ? 'Live' : 'Restarting'}
70
+ </span>
71
+ </div>
44
72
  </aside>
45
73
  );
46
74
  }
@@ -49,11 +77,13 @@ function NavItem({
49
77
  to,
50
78
  icon: Icon,
51
79
  label,
80
+ sub,
52
81
  onNavigate,
53
82
  }: {
54
83
  to: string;
55
84
  icon: React.ComponentType<{ className?: string }>;
56
85
  label: string;
86
+ sub?: string;
57
87
  onNavigate?: () => void;
58
88
  }) {
59
89
  return (
@@ -63,15 +93,18 @@ function NavItem({
63
93
  onClick={onNavigate}
64
94
  className={({ isActive }) =>
65
95
  cn(
66
- 'flex items-center gap-3 w-full px-3 py-2.5 rounded-lg text-sm transition-colors',
96
+ 'flex items-start gap-3 w-full px-3 py-2.5 rounded-lg text-sm transition-colors',
67
97
  isActive
68
98
  ? 'bg-sidebar-accent text-foreground font-medium'
69
99
  : 'text-muted-foreground hover:text-foreground hover:bg-sidebar-accent/50',
70
100
  )
71
101
  }
72
102
  >
73
- <Icon className="h-[18px] w-[18px]" />
74
- {label}
103
+ <Icon className="h-[18px] w-[18px] mt-0.5 shrink-0" />
104
+ <div>
105
+ {label}
106
+ {sub && <p className="text-[10px] text-muted-foreground/50 font-normal leading-tight mt-0.5">{sub}</p>}
107
+ </div>
75
108
  </NavLink>
76
109
  );
77
110
  }