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
@@ -1,80 +1,76 @@
1
1
  const starterSuggestions = [
2
- 'Build me a CRM',
3
- 'Create a habit tracker',
4
- 'Make a finance dashboard',
5
- 'Build a task manager'
2
+ 'Build me a CRM',
3
+ 'Create a habit tracker',
4
+ 'Make a finance dashboard',
5
+ 'Build a task manager',
6
6
  ];
7
7
 
8
8
  export default function DashboardPage() {
9
- const handleSuggestion = (text: string) => {
10
- // Open the chat widget and pre-fill the suggestion
11
- const panel = document.getElementById('fluxy-widget-panel');
12
- if (panel && !panel.classList.contains('open')) {
13
- const toggle = document.getElementById('fluxy-widget-toggle');
14
- toggle?.click();
15
- }
16
- // Give the widget a moment to open, then try to fill the input
17
- setTimeout(() => {
18
- const input = document.querySelector<HTMLTextAreaElement>(
19
- '#fluxy-widget-panel textarea, #fluxy-widget-panel input[type="text"]'
20
- );
21
- if (input) {
22
- const nativeSetter =
23
- Object.getOwnPropertyDescriptor(
24
- window.HTMLTextAreaElement.prototype,
25
- 'value'
26
- )?.set ||
27
- Object.getOwnPropertyDescriptor(
28
- window.HTMLInputElement.prototype,
29
- 'value'
30
- )?.set;
31
- nativeSetter?.call(input, text);
32
- input.dispatchEvent(new Event('input', { bubbles: true }));
33
- input.focus();
34
- }
35
- }, 400);
36
- };
9
+ const handleSuggestion = (text: string) => {
10
+ // Open the chat widget and pre-fill the suggestion
11
+ const panel = document.getElementById('fluxy-widget-panel');
12
+ if (panel && !panel.classList.contains('open')) {
13
+ const toggle = document.getElementById('fluxy-widget-toggle');
14
+ toggle?.click();
15
+ }
16
+ // Give the widget a moment to open, then try to fill the input
17
+ setTimeout(() => {
18
+ const input = document.querySelector<HTMLTextAreaElement>(
19
+ '#fluxy-widget-panel textarea, #fluxy-widget-panel input[type="text"]'
20
+ );
21
+ if (input) {
22
+ const nativeSetter = Object.getOwnPropertyDescriptor(
23
+ window.HTMLTextAreaElement.prototype, 'value'
24
+ )?.set || Object.getOwnPropertyDescriptor(
25
+ window.HTMLInputElement.prototype, 'value'
26
+ )?.set;
27
+ nativeSetter?.call(input, text);
28
+ input.dispatchEvent(new Event('input', { bubbles: true }));
29
+ input.focus();
30
+ }
31
+ }, 400);
32
+ };
37
33
 
38
- return (
39
- <div className="flex flex-col items-center justify-start h-full px-4 pt-16 sm:pt-24">
40
- {/* Welcome message */}
41
- <h1 className="text-2xl sm:text-3xl font-bold text-center mb-2">
42
- Let's get started
43
- </h1>
44
- <p className="text-muted-foreground text-sm sm:text-base text-center max-w-md mb-8">
45
- Tell me what to build and I'll create it for you, right here.
46
- </p>
34
+ return (
35
+ <div className="flex flex-col items-center justify-start h-full px-4 pt-16 sm:pt-24">
36
+ {/* Welcome message */}
37
+ <h1 className="text-2xl sm:text-3xl font-bold text-center mb-2">
38
+ Let's get started
39
+ </h1>
40
+ <p className="text-muted-foreground text-sm sm:text-base text-center max-w-md mb-8">
41
+ Tell me what to build and I'll create it for you, right here.
42
+ </p>
47
43
 
48
- {/* Starter suggestion chips */}
49
- <div className="flex flex-wrap items-center justify-center gap-2 sm:gap-3 max-w-lg mb-12">
50
- {starterSuggestions.map(suggestion => (
51
- <button
52
- key={suggestion}
53
- onClick={() => handleSuggestion(suggestion)}
54
- 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]"
55
- >
56
- {suggestion}
57
- </button>
58
- ))}
59
- </div>
44
+ {/* Starter suggestion chips */}
45
+ <div className="flex flex-wrap items-center justify-center gap-2 sm:gap-3 max-w-lg mb-12">
46
+ {starterSuggestions.map((suggestion) => (
47
+ <button
48
+ key={suggestion}
49
+ onClick={() => handleSuggestion(suggestion)}
50
+ 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]"
51
+ >
52
+ {suggestion}
53
+ </button>
54
+ ))}
55
+ </div>
60
56
 
61
- {/* Arrow pointing to chat */}
62
- {/* Desktop */}
63
- <div
64
- id="chat-arrow-desktop"
65
- className="hidden md:flex fixed flex-col items-center pointer-events-none"
66
- style={{ bottom: 100, right: 100 }}
67
- >
68
- <img src="/arrow.png" alt="" style={{ width: 120 }} />
69
- </div>
70
- {/* Mobile */}
71
- <img
72
- id="chat-arrow-mobile"
73
- src="/arrow.png"
74
- alt=""
75
- className="block md:hidden fixed pointer-events-none"
76
- style={{ width: 90, bottom: 75, right: 60 }}
77
- />
78
- </div>
79
- );
57
+ {/* Arrow pointing to chat */}
58
+ {/* Desktop */}
59
+ <div
60
+ id="chat-arrow-desktop"
61
+ className="hidden md:flex fixed flex-col items-center pointer-events-none"
62
+ style={{ bottom: 100, right: 100 }}
63
+ >
64
+ <img src="/arrow.png" alt="" style={{ width: 120 }} />
65
+ </div>
66
+ {/* Mobile */}
67
+ <img
68
+ id="chat-arrow-mobile"
69
+ src="/arrow.png"
70
+ alt=""
71
+ className="block md:hidden fixed pointer-events-none"
72
+ style={{ width: 90, bottom: 75, right: 60 }}
73
+ />
74
+ </div>
75
+ );
80
76
  }
@@ -1,23 +1,23 @@
1
1
  import { Component, type ReactNode } from 'react';
2
2
 
3
3
  interface Props {
4
- children: ReactNode;
5
- fallback: ReactNode;
4
+ children: ReactNode;
5
+ fallback: ReactNode;
6
6
  }
7
7
 
8
8
  interface State {
9
- hasError: boolean;
9
+ hasError: boolean;
10
10
  }
11
11
 
12
12
  export default class ErrorBoundary extends Component<Props, State> {
13
- state: State = { hasError: false };
13
+ state: State = { hasError: false };
14
14
 
15
- static getDerivedStateFromError(): State {
16
- return { hasError: true };
17
- }
15
+ static getDerivedStateFromError(): State {
16
+ return { hasError: true };
17
+ }
18
18
 
19
- render() {
20
- if (this.state.hasError) return this.props.fallback;
21
- return this.props.children;
22
- }
19
+ render() {
20
+ if (this.state.hasError) return this.props.fallback;
21
+ return this.props.children;
22
+ }
23
23
  }
@@ -5,47 +5,47 @@ import Footer from './Footer';
5
5
  import MobileNav from './MobileNav';
6
6
 
7
7
  interface Props {
8
- children: ReactNode;
8
+ children: ReactNode;
9
9
  }
10
10
 
11
11
  export default function DashboardLayout({ children }: Props) {
12
- const [connected, setConnected] = useState(true);
12
+ const [connected, setConnected] = useState(true);
13
13
 
14
- useEffect(() => {
15
- const check = () => {
16
- fetch('/api/health', { method: 'HEAD' })
17
- .then(() => setConnected(true))
18
- .catch(() => setConnected(false));
19
- };
20
- check();
21
- const id = setInterval(check, 15_000);
22
- return () => clearInterval(id);
23
- }, []);
14
+ useEffect(() => {
15
+ const check = () => {
16
+ fetch('/api/health', { method: 'HEAD' })
17
+ .then(() => setConnected(true))
18
+ .catch(() => setConnected(false));
19
+ };
20
+ check();
21
+ const id = setInterval(check, 15_000);
22
+ return () => clearInterval(id);
23
+ }, []);
24
24
 
25
- return (
26
- <div className="flex h-dvh flex-col bg-background">
27
- {/* Mobile header */}
28
- <header className="flex items-center justify-between px-4 py-3 md:hidden">
29
- <MobileNav />
30
- <div className="flex items-center gap-2">
31
- <img src="/fluxy.png" alt="Fluxy" className="h-6 w-auto" />
32
- <span className="font-semibold text-base">Fluxy</span>
33
- </div>
34
- <div className="w-10" />
35
- </header>
25
+ return (
26
+ <div className="flex h-dvh flex-col bg-background">
27
+ {/* Mobile header */}
28
+ <header className="flex items-center justify-between px-4 py-3 md:hidden">
29
+ <MobileNav />
30
+ <div className="flex items-center gap-2">
31
+ <img src="/fluxy.png" alt="Fluxy" className="h-6 w-auto" />
32
+ <span className="font-semibold text-base">Fluxy</span>
33
+ </div>
34
+ <div className="w-10" />
35
+ </header>
36
36
 
37
- <div className="flex flex-1 overflow-hidden">
38
- {/* Desktop sidebar */}
39
- <div className="hidden md:flex shrink-0">
40
- <Sidebar />
41
- </div>
37
+ <div className="flex flex-1 overflow-hidden">
38
+ {/* Desktop sidebar */}
39
+ <div className="hidden md:flex shrink-0">
40
+ <Sidebar />
41
+ </div>
42
42
 
43
- {/* Main content + footer */}
44
- <div className="flex flex-1 flex-col overflow-hidden">
45
- <main className="flex-1 overflow-y-auto">{children}</main>
46
- <Footer connected={connected} />
47
- </div>
48
- </div>
43
+ {/* Main content + footer */}
44
+ <div className="flex flex-1 flex-col overflow-hidden">
45
+ <main className="flex-1 overflow-y-auto">{children}</main>
46
+ <Footer connected={connected} />
49
47
  </div>
50
- );
48
+ </div>
49
+ </div>
50
+ );
51
51
  }
@@ -1,14 +1,14 @@
1
1
  export default function Footer({ connected }: { connected: boolean }) {
2
- return (
3
- <footer className="flex items-center px-4 md:px-6 py-2.5 text-[11px] text-muted-foreground/60 shrink-0">
4
- <div className="flex items-center gap-1.5">
5
- <div
6
- className={`h-1.5 w-1.5 rounded-full ${
7
- connected ? 'bg-emerald-500' : 'bg-red-500'
8
- }`}
9
- />
10
- <span>{connected ? 'Connected' : 'Disconnected'}</span>
11
- </div>
12
- </footer>
13
- );
2
+ return (
3
+ <footer className="flex items-center px-4 md:px-6 py-2.5 text-[11px] text-muted-foreground/60 shrink-0">
4
+ <div className="flex items-center gap-1.5">
5
+ <div
6
+ className={`h-1.5 w-1.5 rounded-full ${
7
+ connected ? 'bg-emerald-500' : 'bg-red-500'
8
+ }`}
9
+ />
10
+ <span>{connected ? 'Connected' : 'Disconnected'}</span>
11
+ </div>
12
+ </footer>
13
+ );
14
14
  }
@@ -1,30 +1,30 @@
1
1
  import { useState } from 'react';
2
2
  import { Menu } from 'lucide-react';
3
- import { Sheet, SheetContent, SheetTitle } from '@/components/ui/sheet';
3
+ import {
4
+ Sheet,
5
+ SheetContent,
6
+ SheetTitle,
7
+ } from '@/components/ui/sheet';
4
8
  import Sidebar from './Sidebar';
5
9
 
6
10
  export default function MobileNav() {
7
- const [open, setOpen] = useState(false);
11
+ const [open, setOpen] = useState(false);
8
12
 
9
- return (
10
- <>
11
- <button
12
- onClick={() => setOpen(true)}
13
- className="flex items-center justify-center h-10 w-10 rounded-lg text-muted-foreground hover:text-foreground transition-colors md:hidden"
14
- >
15
- <Menu className="h-5 w-5" />
16
- <span className="sr-only">Open navigation</span>
17
- </button>
18
- <Sheet open={open} onOpenChange={setOpen}>
19
- <SheetContent
20
- side="left"
21
- className="p-0 w-64"
22
- showCloseButton={false}
23
- >
24
- <SheetTitle className="sr-only">Navigation</SheetTitle>
25
- <Sidebar />
26
- </SheetContent>
27
- </Sheet>
28
- </>
29
- );
13
+ return (
14
+ <>
15
+ <button
16
+ onClick={() => setOpen(true)}
17
+ className="flex items-center justify-center h-10 w-10 rounded-lg text-muted-foreground hover:text-foreground transition-colors md:hidden"
18
+ >
19
+ <Menu className="h-5 w-5" />
20
+ <span className="sr-only">Open navigation</span>
21
+ </button>
22
+ <Sheet open={open} onOpenChange={setOpen}>
23
+ <SheetContent side="left" className="p-0 w-64" showCloseButton={false}>
24
+ <SheetTitle className="sr-only">Navigation</SheetTitle>
25
+ <Sidebar />
26
+ </SheetContent>
27
+ </Sheet>
28
+ </>
29
+ );
30
30
  }
@@ -1,102 +1,102 @@
1
1
  import { useState } from 'react';
2
2
  import {
3
- LayoutDashboard,
4
- AppWindow,
5
- BarChart3,
6
- Search,
7
- HelpCircle,
8
- ChevronDown
3
+ LayoutDashboard,
4
+ AppWindow,
5
+ BarChart3,
6
+ Search,
7
+ HelpCircle,
8
+ ChevronDown,
9
9
  } from 'lucide-react';
10
10
  import { cn } from '@/lib/utils';
11
11
 
12
12
  function getGreeting(): string {
13
- const hour = new Date().getHours();
14
- if (hour < 12) return 'Good\nMorning';
15
- if (hour < 18) return 'Good\nAfternoon';
16
- return 'Good\nEvening';
13
+ const hour = new Date().getHours();
14
+ if (hour < 12) return 'Good\nMorning';
15
+ if (hour < 18) return 'Good\nAfternoon';
16
+ return 'Good\nEvening';
17
17
  }
18
18
 
19
19
  export default function Sidebar() {
20
- const [appsOpen, setAppsOpen] = useState(false);
20
+ const [appsOpen, setAppsOpen] = useState(false);
21
21
 
22
- return (
23
- <aside className="flex flex-col h-full w-64 border-r border-border/50 bg-sidebar p-5 pt-8">
24
- {/* Logo */}
25
- <div className="flex items-center gap-2.5 mb-8">
26
- <img src="/fluxy.png" alt="Fluxy" className="h-7 w-auto" />
27
- <span className="font-semibold text-lg">Fluxy</span>
28
- </div>
22
+ return (
23
+ <aside className="flex flex-col h-full w-64 border-r border-border/50 bg-sidebar p-5 pt-8">
24
+ {/* Logo */}
25
+ <div className="flex items-center gap-2.5 mb-8">
26
+ <img src="/fluxy.png" alt="Fluxy" className="h-7 w-auto" />
27
+ <span className="font-semibold text-lg">Fluxy</span>
28
+ </div>
29
29
 
30
- {/* Greeting */}
31
- <div className="mb-10">
32
- <h1 className="text-4xl font-bold leading-[1.1] whitespace-pre-line">
33
- {getGreeting()}
34
- </h1>
35
- <h2 className="text-4xl font-bold text-primary mt-0.5"></h2>
36
- </div>
30
+ {/* Greeting */}
31
+ <div className="mb-10">
32
+ <h1 className="text-4xl font-bold leading-[1.1] whitespace-pre-line">
33
+ {getGreeting()}
34
+ </h1>
35
+ <h2 className="text-4xl font-bold text-primary mt-0.5"></h2>
36
+ </div>
37
37
 
38
- {/* Navigation */}
39
- <nav className="flex-1 space-y-0.5">
40
- <NavButton icon={LayoutDashboard} label="Dashboard" active />
38
+ {/* Navigation */}
39
+ <nav className="flex-1 space-y-0.5">
40
+ <NavButton icon={LayoutDashboard} label="Dashboard" active />
41
41
 
42
- {/* My Apps with dropdown */}
43
- <div>
44
- <button
45
- onClick={() => setAppsOpen(!appsOpen)}
46
- className="flex items-center gap-3 w-full px-3 py-2.5 rounded-lg text-sm text-muted-foreground hover:text-foreground hover:bg-sidebar-accent/50 transition-colors"
47
- >
48
- <AppWindow className="h-[18px] w-[18px]" />
49
- My Apps
50
- <ChevronDown
51
- className={cn(
52
- 'h-3.5 w-3.5 ml-auto transition-transform duration-200',
53
- appsOpen && 'rotate-180'
54
- )}
55
- />
56
- </button>
57
- {appsOpen && (
58
- <div className="ml-9 mt-0.5 space-y-0.5">
59
- <button className="block w-full text-left px-3 py-2 rounded-lg text-sm text-muted-foreground hover:text-foreground hover:bg-sidebar-accent/50 transition-colors">
60
- App 1
61
- </button>
62
- <button className="block w-full text-left px-3 py-2 rounded-lg text-sm text-muted-foreground hover:text-foreground hover:bg-sidebar-accent/50 transition-colors">
63
- App 2
64
- </button>
65
- </div>
66
- )}
67
- </div>
42
+ {/* My Apps with dropdown */}
43
+ <div>
44
+ <button
45
+ onClick={() => setAppsOpen(!appsOpen)}
46
+ className="flex items-center gap-3 w-full px-3 py-2.5 rounded-lg text-sm text-muted-foreground hover:text-foreground hover:bg-sidebar-accent/50 transition-colors"
47
+ >
48
+ <AppWindow className="h-[18px] w-[18px]" />
49
+ My Apps
50
+ <ChevronDown
51
+ className={cn(
52
+ 'h-3.5 w-3.5 ml-auto transition-transform duration-200',
53
+ appsOpen && 'rotate-180',
54
+ )}
55
+ />
56
+ </button>
57
+ {appsOpen && (
58
+ <div className="ml-9 mt-0.5 space-y-0.5">
59
+ <button className="block w-full text-left px-3 py-2 rounded-lg text-sm text-muted-foreground hover:text-foreground hover:bg-sidebar-accent/50 transition-colors">
60
+ App 1
61
+ </button>
62
+ <button className="block w-full text-left px-3 py-2 rounded-lg text-sm text-muted-foreground hover:text-foreground hover:bg-sidebar-accent/50 transition-colors">
63
+ App 2
64
+ </button>
65
+ </div>
66
+ )}
67
+ </div>
68
68
 
69
- <NavButton icon={BarChart3} label="Reports" />
70
- <NavButton icon={Search} label="Research" />
71
- <NavButton icon={HelpCircle} label="What Else?" />
72
- </nav>
69
+ <NavButton icon={BarChart3} label="Reports" />
70
+ <NavButton icon={Search} label="Research" />
71
+ <NavButton icon={HelpCircle} label="What Else?" />
72
+ </nav>
73
73
 
74
- {/* Bottom spacer */}
75
- <div className="pt-4 border-t border-border/50" />
76
- </aside>
77
- );
74
+ {/* Bottom spacer */}
75
+ <div className="pt-4 border-t border-border/50" />
76
+ </aside>
77
+ );
78
78
  }
79
79
 
80
80
  function NavButton({
81
- icon: Icon,
82
- label,
83
- active
81
+ icon: Icon,
82
+ label,
83
+ active,
84
84
  }: {
85
- icon: React.ComponentType<{ className?: string }>;
86
- label: string;
87
- active?: boolean;
85
+ icon: React.ComponentType<{ className?: string }>;
86
+ label: string;
87
+ active?: boolean;
88
88
  }) {
89
- return (
90
- <button
91
- className={cn(
92
- 'flex items-center gap-3 w-full px-3 py-2.5 rounded-lg text-sm transition-colors',
93
- active
94
- ? 'bg-sidebar-accent text-foreground font-medium'
95
- : 'text-muted-foreground hover:text-foreground hover:bg-sidebar-accent/50'
96
- )}
97
- >
98
- <Icon className="h-[18px] w-[18px]" />
99
- {label}
100
- </button>
101
- );
89
+ return (
90
+ <button
91
+ className={cn(
92
+ 'flex items-center gap-3 w-full px-3 py-2.5 rounded-lg text-sm transition-colors',
93
+ active
94
+ ? 'bg-sidebar-accent text-foreground font-medium'
95
+ : 'text-muted-foreground hover:text-foreground hover:bg-sidebar-accent/50',
96
+ )}
97
+ >
98
+ <Icon className="h-[18px] w-[18px]" />
99
+ {label}
100
+ </button>
101
+ );
102
102
  }