beads-kanban-ui 0.1.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 (154) hide show
  1. package/.designs/beads-kanban-ui-bj0.md +73 -0
  2. package/.designs/beads-kanban-ui-qxq.md +144 -0
  3. package/.designs/epic-support.md +282 -0
  4. package/.env.local.example +2 -0
  5. package/.eslintrc.json +3 -0
  6. package/.gitattributes +3 -0
  7. package/.github/workflows/release.yml +123 -0
  8. package/.history/README_20260121193710.md +227 -0
  9. package/.history/README_20260121193918.md +227 -0
  10. package/.history/README_20260121193921.md +227 -0
  11. package/.history/README_20260121193933.md +227 -0
  12. package/.history/README_20260121193934.md +227 -0
  13. package/.history/README_20260121193944.md +227 -0
  14. package/.history/README_20260121193953.md +227 -0
  15. package/.history/src/app/page_20260121133429.tsx +134 -0
  16. package/.history/src/app/page_20260121133928.tsx +134 -0
  17. package/.history/src/app/page_20260121144850.tsx +138 -0
  18. package/.history/src/app/page_20260121144854.tsx +138 -0
  19. package/.history/src/app/page_20260121144858.tsx +138 -0
  20. package/.history/src/app/page_20260121144902.tsx +138 -0
  21. package/.history/src/app/page_20260121144906.tsx +138 -0
  22. package/.history/src/app/page_20260121144911.tsx +138 -0
  23. package/.history/src/app/page_20260121144928.tsx +138 -0
  24. package/.playwright-mcp/.playwright-mcp/morphing-dialog-wheel-scroll-fix.png +0 -0
  25. package/.playwright-mcp/beams-test.png +0 -0
  26. package/.playwright-mcp/card-verification.png +0 -0
  27. package/.playwright-mcp/design-doc-dialog-fix-verification.png +0 -0
  28. package/.playwright-mcp/dialog-width-test.png +0 -0
  29. package/.playwright-mcp/homepage.png +0 -0
  30. package/.playwright-mcp/morphing-dialog-expanded.png +0 -0
  31. package/.playwright-mcp/morphing-dialog-fixes-final.png +0 -0
  32. package/.playwright-mcp/morphing-dialog-open.png +0 -0
  33. package/.playwright-mcp/page-2026-01-21T14-08-31-529Z.png +0 -0
  34. package/.playwright-mcp/page-2026-01-21T14-09-23-431Z.png +0 -0
  35. package/.playwright-mcp/page-2026-01-21T14-10-28-773Z.png +0 -0
  36. package/.playwright-mcp/page-2026-01-21T14-10-47-432Z.png +0 -0
  37. package/.playwright-mcp/page-2026-01-21T14-11-12-350Z.png +0 -0
  38. package/.playwright-mcp/screenshot-after-click.png +0 -0
  39. package/.playwright-mcp/screenshot-after-dialog-click.png +0 -0
  40. package/.playwright-mcp/sheet-restored-after-dialog-close.png +0 -0
  41. package/.playwright-mcp/test-1-sheet-open-with-overlay.png +0 -0
  42. package/.playwright-mcp/test-2-morphing-dialog-with-overlay.png +0 -0
  43. package/.playwright-mcp/test-3-sheet-open-dark-overlay.png +0 -0
  44. package/.playwright-mcp/test-4-morphing-dialog-with-dark-overlay.png +0 -0
  45. package/.playwright-mcp/test-5-morphing-dialog-scrolled.png +0 -0
  46. package/.playwright-mcp/test-6-sheet-restored-after-dialog-close.png +0 -0
  47. package/.playwright-mcp/wheel-scroll-fixed.png +0 -0
  48. package/README.md +243 -0
  49. package/Screenshots/bead-detail.png +0 -0
  50. package/Screenshots/dashboard.png +0 -0
  51. package/Screenshots/kanban-board.png +0 -0
  52. package/components.json +27 -0
  53. package/logo/logo.svg +1 -0
  54. package/next.config.js +9 -0
  55. package/npm/README.md +37 -0
  56. package/npm/bin/cli.js +107 -0
  57. package/npm/package.json +20 -0
  58. package/npm/scripts/postinstall.js +132 -0
  59. package/package.json +62 -0
  60. package/postcss.config.js +6 -0
  61. package/public/logo.svg +1 -0
  62. package/restart.sh +5 -0
  63. package/server/Cargo.lock +1685 -0
  64. package/server/Cargo.toml +24 -0
  65. package/server/src/db.rs +570 -0
  66. package/server/src/main.rs +141 -0
  67. package/server/src/routes/beads.rs +413 -0
  68. package/server/src/routes/cli.rs +150 -0
  69. package/server/src/routes/fs.rs +360 -0
  70. package/server/src/routes/git.rs +169 -0
  71. package/server/src/routes/mod.rs +107 -0
  72. package/server/src/routes/projects.rs +177 -0
  73. package/server/src/routes/watch.rs +211 -0
  74. package/src/app/globals.css +101 -0
  75. package/src/app/layout.tsx +36 -0
  76. package/src/app/page.tsx +348 -0
  77. package/src/app/project/kanban-board.tsx +356 -0
  78. package/src/app/project/page.tsx +18 -0
  79. package/src/app/settings/page.tsx +224 -0
  80. package/src/components/Beams.css +5 -0
  81. package/src/components/Beams.jsx +307 -0
  82. package/src/components/Galaxy.css +5 -0
  83. package/src/components/Galaxy.jsx +333 -0
  84. package/src/components/activity-timeline.tsx +172 -0
  85. package/src/components/add-project-dialog.tsx +219 -0
  86. package/src/components/bead-card.tsx +196 -0
  87. package/src/components/bead-detail.tsx +306 -0
  88. package/src/components/color-picker.tsx +101 -0
  89. package/src/components/comment-input.tsx +155 -0
  90. package/src/components/comment-list.tsx +147 -0
  91. package/src/components/dependency-badge.tsx +106 -0
  92. package/src/components/design-doc-dialog.tsx +58 -0
  93. package/src/components/design-doc-preview.tsx +97 -0
  94. package/src/components/design-doc-viewer.tsx +199 -0
  95. package/src/components/editable-project-name.tsx +178 -0
  96. package/src/components/epic-card.tsx +263 -0
  97. package/src/components/folder-browser.tsx +273 -0
  98. package/src/components/footer.tsx +27 -0
  99. package/src/components/kanban/default.tsx +184 -0
  100. package/src/components/kanban-column.tsx +167 -0
  101. package/src/components/project-card.tsx +191 -0
  102. package/src/components/quick-filter-bar.tsx +279 -0
  103. package/src/components/scan-directory-dialog.tsx +368 -0
  104. package/src/components/status-donut.tsx +197 -0
  105. package/src/components/subtask-list.tsx +128 -0
  106. package/src/components/tag-picker.tsx +252 -0
  107. package/src/components/ui/.gitkeep +0 -0
  108. package/src/components/ui/alert-dialog.tsx +141 -0
  109. package/src/components/ui/avatar.tsx +67 -0
  110. package/src/components/ui/badge.tsx +230 -0
  111. package/src/components/ui/button.tsx +433 -0
  112. package/src/components/ui/card/index.tsx +24 -0
  113. package/src/components/ui/card/roiui-card.module.css +197 -0
  114. package/src/components/ui/card/roiui-card.tsx +154 -0
  115. package/src/components/ui/card/shadcn-card.tsx +76 -0
  116. package/src/components/ui/chart.tsx +369 -0
  117. package/src/components/ui/dialog.tsx +122 -0
  118. package/src/components/ui/dropdown-menu.tsx +201 -0
  119. package/src/components/ui/input.tsx +22 -0
  120. package/src/components/ui/kanban.tsx +522 -0
  121. package/src/components/ui/morphing-dialog.tsx +457 -0
  122. package/src/components/ui/popover.tsx +33 -0
  123. package/src/components/ui/progress.tsx +28 -0
  124. package/src/components/ui/scroll-area.tsx +48 -0
  125. package/src/components/ui/select.tsx +159 -0
  126. package/src/components/ui/separator.tsx +31 -0
  127. package/src/components/ui/sheet.tsx +142 -0
  128. package/src/components/ui/skeleton.tsx +15 -0
  129. package/src/components/ui/toast.tsx +129 -0
  130. package/src/components/ui/toaster.tsx +35 -0
  131. package/src/components/ui/tooltip.tsx +30 -0
  132. package/src/hooks/.gitkeep +0 -0
  133. package/src/hooks/use-bead-filters.ts +261 -0
  134. package/src/hooks/use-beads.ts +162 -0
  135. package/src/hooks/use-branch-statuses.ts +161 -0
  136. package/src/hooks/use-epics.ts +173 -0
  137. package/src/hooks/use-file-watcher.ts +111 -0
  138. package/src/hooks/use-keyboard-navigation.ts +282 -0
  139. package/src/hooks/use-project.ts +61 -0
  140. package/src/hooks/use-projects.ts +93 -0
  141. package/src/hooks/use-toast.ts +194 -0
  142. package/src/hooks/useClickOutside.tsx +26 -0
  143. package/src/lib/.gitkeep +0 -0
  144. package/src/lib/api.ts +186 -0
  145. package/src/lib/beads-parser.ts +252 -0
  146. package/src/lib/cli.ts +193 -0
  147. package/src/lib/db.ts +145 -0
  148. package/src/lib/design-doc.ts +74 -0
  149. package/src/lib/epic-parser.ts +242 -0
  150. package/src/lib/git.ts +102 -0
  151. package/src/lib/utils.ts +12 -0
  152. package/src/types/index.ts +107 -0
  153. package/tailwind.config.ts +85 -0
  154. package/tsconfig.json +26 -0
@@ -0,0 +1,138 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import Link from "next/link";
5
+ import dynamic from "next/dynamic";
6
+ import { Settings } from "lucide-react";
7
+ import { ProjectCard } from "@/components/project-card";
8
+ import { AddProjectDialog } from "@/components/add-project-dialog";
9
+ import { useProjects } from "@/hooks/use-projects";
10
+ import { Skeleton } from "@/components/ui/skeleton";
11
+
12
+ // Dynamic import with SSR disabled for WebGL canvas component
13
+ const RippleGrid = dynamic(() => import("@/components/RippleGrid"), { ssr: false });
14
+
15
+ export default function ProjectsPage() {
16
+ const [isAddDialogOpen, setIsAddDialogOpen] = useState(false);
17
+ const { projects, isLoading, error, addProject, updateProjectTags } = useProjects();
18
+
19
+ const handleAddProject = async (input: { name: string; path: string }) => {
20
+ await addProject(input);
21
+ };
22
+
23
+ return (
24
+ <div className="dark relative min-h-screen bg-[#0d0b14]">
25
+ {/* RippleGrid Background - fixed, full-screen, z-0 */}
26
+ <div className="fixed inset-0 z-0 overflow-hidden">
27
+ <RippleGrid
28
+ gridColor="#302a4c"
29
+ rippleIntensity={0.02}
30
+ gridSize={15}
31
+ gridThickness={33}
32
+ fadeDistance={2.7}
33
+ vignetteStrength={3.5}
34
+ glowIntensity={0.5}
35
+ opacity={1}
36
+ gridRotation={0}
37
+ mouseInteractionRadius={0.8}
38
+ mouseInteraction={false}
39
+ enableRainbow={false}
40
+ />
41
+ </div>
42
+
43
+ {/* Settings Icon - fixed top-right, z-20 */}
44
+ <Link
45
+ href="/settings"
46
+ aria-label="Settings"
47
+ className="fixed right-6 top-6 z-20 rounded-md p-2 text-zinc-400 hover:bg-zinc-800/50 hover:text-zinc-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-400 focus-visible:ring-offset-2 focus-visible:ring-offset-transparent"
48
+ >
49
+ <Settings className="h-5 w-5" aria-hidden="true" />
50
+ </Link>
51
+
52
+ {/* Main Content */}
53
+ <main className="relative z-10 flex flex-col items-center px-6 py-16">
54
+ {/* Centered Heading with Space Grotesk */}
55
+ <h1 className="mb-12 text-center text-balance font-heading text-4xl font-bold tracking-tight text-white sm:text-5xl">
56
+ Manage Your Beads Projects
57
+ </h1>
58
+
59
+ <div className="w-full max-w-[1200px]">
60
+ {isLoading ? (
61
+ <div role="status" aria-label="Loading projects" className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
62
+ {[1, 2, 3].map((i) => (
63
+ <div key={i} className="rounded-xl border bg-card/70 p-4 backdrop-blur-md">
64
+ <div className="mb-3 flex gap-1.5">
65
+ <Skeleton className="h-5 w-16" />
66
+ <Skeleton className="h-5 w-12" />
67
+ </div>
68
+ <Skeleton className="h-5 w-40" />
69
+ <Skeleton className="mt-2 h-4 w-48" />
70
+ <Skeleton className="mt-4 h-4 w-32" />
71
+ <Skeleton className="mt-2 h-3 w-28" />
72
+ </div>
73
+ ))}
74
+ </div>
75
+ ) : error ? (
76
+ <div className="rounded-lg border border-red-800/50 bg-red-950/70 p-6 text-center backdrop-blur-md">
77
+ <p className="text-red-400">Error loading projects: {error.message}</p>
78
+ <p className="mt-2 text-sm text-red-500">
79
+ Make sure the Tauri backend is running.
80
+ </p>
81
+ </div>
82
+ ) : projects.length === 0 ? (
83
+ <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
84
+ <div className="rounded-lg border border-dashed border-zinc-700 bg-card/70 p-6 text-center text-zinc-400 backdrop-blur-md">
85
+ <p>No projects yet</p>
86
+ <p className="mt-1 text-sm text-zinc-500">Click the button below to add a project</p>
87
+ </div>
88
+ </div>
89
+ ) : (
90
+ <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
91
+ {projects.map((project) => (
92
+ <ProjectCard
93
+ key={project.id}
94
+ id={project.id}
95
+ name={project.name}
96
+ path={project.path}
97
+ tags={project.tags}
98
+ beadCounts={project.beadCounts}
99
+ onTagsChange={(tags) => updateProjectTags(project.id, tags)}
100
+ />
101
+ ))}
102
+ </div>
103
+ )}
104
+ </div>
105
+ </main>
106
+
107
+ {/* FAB - Add Project, z-20 */}
108
+ <button
109
+ onClick={() => setIsAddDialogOpen(true)}
110
+ className="fixed bottom-6 right-6 z-20 flex h-14 w-14 items-center justify-center rounded-full bg-zinc-100 text-zinc-900 shadow-lg hover:bg-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-100 focus-visible:ring-offset-2 focus-visible:ring-offset-transparent"
111
+ aria-label="Add Project"
112
+ >
113
+ <svg
114
+ xmlns="http://www.w3.org/2000/svg"
115
+ width="24"
116
+ height="24"
117
+ viewBox="0 0 24 24"
118
+ fill="none"
119
+ stroke="currentColor"
120
+ strokeWidth="2"
121
+ strokeLinecap="round"
122
+ strokeLinejoin="round"
123
+ aria-hidden="true"
124
+ >
125
+ <path d="M5 12h14" />
126
+ <path d="M12 5v14" />
127
+ </svg>
128
+ </button>
129
+
130
+ {/* Add Project Dialog */}
131
+ <AddProjectDialog
132
+ open={isAddDialogOpen}
133
+ onOpenChange={setIsAddDialogOpen}
134
+ onAddProject={handleAddProject}
135
+ />
136
+ </div>
137
+ );
138
+ }
@@ -0,0 +1,138 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import Link from "next/link";
5
+ import dynamic from "next/dynamic";
6
+ import { Settings } from "lucide-react";
7
+ import { ProjectCard } from "@/components/project-card";
8
+ import { AddProjectDialog } from "@/components/add-project-dialog";
9
+ import { useProjects } from "@/hooks/use-projects";
10
+ import { Skeleton } from "@/components/ui/skeleton";
11
+
12
+ // Dynamic import with SSR disabled for WebGL canvas component
13
+ const RippleGrid = dynamic(() => import("@/components/RippleGrid"), { ssr: false });
14
+
15
+ export default function ProjectsPage() {
16
+ const [isAddDialogOpen, setIsAddDialogOpen] = useState(false);
17
+ const { projects, isLoading, error, addProject, updateProjectTags } = useProjects();
18
+
19
+ const handleAddProject = async (input: { name: string; path: string }) => {
20
+ await addProject(input);
21
+ };
22
+
23
+ return (
24
+ <div className="dark relative min-h-screen bg-[#0d0b14]">
25
+ {/* RippleGrid Background - fixed, full-screen, z-0 */}
26
+ <div className="fixed inset-0 z-0 overflow-hidden">
27
+ <RippleGrid
28
+ gridColor="#302a4c"
29
+ rippleIntensity={0.02}
30
+ gridSize={15}
31
+ gridThickness={15}
32
+ fadeDistance={2.7}
33
+ vignetteStrength={3.5}
34
+ glowIntensity={0.5}
35
+ opacity={1}
36
+ gridRotation={0}
37
+ mouseInteractionRadius={0.8}
38
+ mouseInteraction={false}
39
+ enableRainbow={false}
40
+ />
41
+ </div>
42
+
43
+ {/* Settings Icon - fixed top-right, z-20 */}
44
+ <Link
45
+ href="/settings"
46
+ aria-label="Settings"
47
+ className="fixed right-6 top-6 z-20 rounded-md p-2 text-zinc-400 hover:bg-zinc-800/50 hover:text-zinc-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-400 focus-visible:ring-offset-2 focus-visible:ring-offset-transparent"
48
+ >
49
+ <Settings className="h-5 w-5" aria-hidden="true" />
50
+ </Link>
51
+
52
+ {/* Main Content */}
53
+ <main className="relative z-10 flex flex-col items-center px-6 py-16">
54
+ {/* Centered Heading with Space Grotesk */}
55
+ <h1 className="mb-12 text-center text-balance font-heading text-4xl font-bold tracking-tight text-white sm:text-5xl">
56
+ Manage Your Beads Projects
57
+ </h1>
58
+
59
+ <div className="w-full max-w-[1200px]">
60
+ {isLoading ? (
61
+ <div role="status" aria-label="Loading projects" className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
62
+ {[1, 2, 3].map((i) => (
63
+ <div key={i} className="rounded-xl border bg-card/70 p-4 backdrop-blur-md">
64
+ <div className="mb-3 flex gap-1.5">
65
+ <Skeleton className="h-5 w-16" />
66
+ <Skeleton className="h-5 w-12" />
67
+ </div>
68
+ <Skeleton className="h-5 w-40" />
69
+ <Skeleton className="mt-2 h-4 w-48" />
70
+ <Skeleton className="mt-4 h-4 w-32" />
71
+ <Skeleton className="mt-2 h-3 w-28" />
72
+ </div>
73
+ ))}
74
+ </div>
75
+ ) : error ? (
76
+ <div className="rounded-lg border border-red-800/50 bg-red-950/70 p-6 text-center backdrop-blur-md">
77
+ <p className="text-red-400">Error loading projects: {error.message}</p>
78
+ <p className="mt-2 text-sm text-red-500">
79
+ Make sure the Tauri backend is running.
80
+ </p>
81
+ </div>
82
+ ) : projects.length === 0 ? (
83
+ <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
84
+ <div className="rounded-lg border border-dashed border-zinc-700 bg-card/70 p-6 text-center text-zinc-400 backdrop-blur-md">
85
+ <p>No projects yet</p>
86
+ <p className="mt-1 text-sm text-zinc-500">Click the button below to add a project</p>
87
+ </div>
88
+ </div>
89
+ ) : (
90
+ <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
91
+ {projects.map((project) => (
92
+ <ProjectCard
93
+ key={project.id}
94
+ id={project.id}
95
+ name={project.name}
96
+ path={project.path}
97
+ tags={project.tags}
98
+ beadCounts={project.beadCounts}
99
+ onTagsChange={(tags) => updateProjectTags(project.id, tags)}
100
+ />
101
+ ))}
102
+ </div>
103
+ )}
104
+ </div>
105
+ </main>
106
+
107
+ {/* FAB - Add Project, z-20 */}
108
+ <button
109
+ onClick={() => setIsAddDialogOpen(true)}
110
+ className="fixed bottom-6 right-6 z-20 flex h-14 w-14 items-center justify-center rounded-full bg-zinc-100 text-zinc-900 shadow-lg hover:bg-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-100 focus-visible:ring-offset-2 focus-visible:ring-offset-transparent"
111
+ aria-label="Add Project"
112
+ >
113
+ <svg
114
+ xmlns="http://www.w3.org/2000/svg"
115
+ width="24"
116
+ height="24"
117
+ viewBox="0 0 24 24"
118
+ fill="none"
119
+ stroke="currentColor"
120
+ strokeWidth="2"
121
+ strokeLinecap="round"
122
+ strokeLinejoin="round"
123
+ aria-hidden="true"
124
+ >
125
+ <path d="M5 12h14" />
126
+ <path d="M12 5v14" />
127
+ </svg>
128
+ </button>
129
+
130
+ {/* Add Project Dialog */}
131
+ <AddProjectDialog
132
+ open={isAddDialogOpen}
133
+ onOpenChange={setIsAddDialogOpen}
134
+ onAddProject={handleAddProject}
135
+ />
136
+ </div>
137
+ );
138
+ }
@@ -0,0 +1,138 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import Link from "next/link";
5
+ import dynamic from "next/dynamic";
6
+ import { Settings } from "lucide-react";
7
+ import { ProjectCard } from "@/components/project-card";
8
+ import { AddProjectDialog } from "@/components/add-project-dialog";
9
+ import { useProjects } from "@/hooks/use-projects";
10
+ import { Skeleton } from "@/components/ui/skeleton";
11
+
12
+ // Dynamic import with SSR disabled for WebGL canvas component
13
+ const RippleGrid = dynamic(() => import("@/components/RippleGrid"), { ssr: false });
14
+
15
+ export default function ProjectsPage() {
16
+ const [isAddDialogOpen, setIsAddDialogOpen] = useState(false);
17
+ const { projects, isLoading, error, addProject, updateProjectTags } = useProjects();
18
+
19
+ const handleAddProject = async (input: { name: string; path: string }) => {
20
+ await addProject(input);
21
+ };
22
+
23
+ return (
24
+ <div className="dark relative min-h-screen bg-[#0d0b14]">
25
+ {/* RippleGrid Background - fixed, full-screen, z-0 */}
26
+ <div className="fixed inset-0 z-0 overflow-hidden">
27
+ <RippleGrid
28
+ gridColor="#302a4c"
29
+ rippleIntensity={0.02}
30
+ gridSize={15}
31
+ gridThickness={15}
32
+ fadeDistance={1.8}
33
+ vignetteStrength={3.5}
34
+ glowIntensity={0.5}
35
+ opacity={1}
36
+ gridRotation={0}
37
+ mouseInteractionRadius={0.8}
38
+ mouseInteraction={false}
39
+ enableRainbow={false}
40
+ />
41
+ </div>
42
+
43
+ {/* Settings Icon - fixed top-right, z-20 */}
44
+ <Link
45
+ href="/settings"
46
+ aria-label="Settings"
47
+ className="fixed right-6 top-6 z-20 rounded-md p-2 text-zinc-400 hover:bg-zinc-800/50 hover:text-zinc-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-400 focus-visible:ring-offset-2 focus-visible:ring-offset-transparent"
48
+ >
49
+ <Settings className="h-5 w-5" aria-hidden="true" />
50
+ </Link>
51
+
52
+ {/* Main Content */}
53
+ <main className="relative z-10 flex flex-col items-center px-6 py-16">
54
+ {/* Centered Heading with Space Grotesk */}
55
+ <h1 className="mb-12 text-center text-balance font-heading text-4xl font-bold tracking-tight text-white sm:text-5xl">
56
+ Manage Your Beads Projects
57
+ </h1>
58
+
59
+ <div className="w-full max-w-[1200px]">
60
+ {isLoading ? (
61
+ <div role="status" aria-label="Loading projects" className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
62
+ {[1, 2, 3].map((i) => (
63
+ <div key={i} className="rounded-xl border bg-card/70 p-4 backdrop-blur-md">
64
+ <div className="mb-3 flex gap-1.5">
65
+ <Skeleton className="h-5 w-16" />
66
+ <Skeleton className="h-5 w-12" />
67
+ </div>
68
+ <Skeleton className="h-5 w-40" />
69
+ <Skeleton className="mt-2 h-4 w-48" />
70
+ <Skeleton className="mt-4 h-4 w-32" />
71
+ <Skeleton className="mt-2 h-3 w-28" />
72
+ </div>
73
+ ))}
74
+ </div>
75
+ ) : error ? (
76
+ <div className="rounded-lg border border-red-800/50 bg-red-950/70 p-6 text-center backdrop-blur-md">
77
+ <p className="text-red-400">Error loading projects: {error.message}</p>
78
+ <p className="mt-2 text-sm text-red-500">
79
+ Make sure the Tauri backend is running.
80
+ </p>
81
+ </div>
82
+ ) : projects.length === 0 ? (
83
+ <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
84
+ <div className="rounded-lg border border-dashed border-zinc-700 bg-card/70 p-6 text-center text-zinc-400 backdrop-blur-md">
85
+ <p>No projects yet</p>
86
+ <p className="mt-1 text-sm text-zinc-500">Click the button below to add a project</p>
87
+ </div>
88
+ </div>
89
+ ) : (
90
+ <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
91
+ {projects.map((project) => (
92
+ <ProjectCard
93
+ key={project.id}
94
+ id={project.id}
95
+ name={project.name}
96
+ path={project.path}
97
+ tags={project.tags}
98
+ beadCounts={project.beadCounts}
99
+ onTagsChange={(tags) => updateProjectTags(project.id, tags)}
100
+ />
101
+ ))}
102
+ </div>
103
+ )}
104
+ </div>
105
+ </main>
106
+
107
+ {/* FAB - Add Project, z-20 */}
108
+ <button
109
+ onClick={() => setIsAddDialogOpen(true)}
110
+ className="fixed bottom-6 right-6 z-20 flex h-14 w-14 items-center justify-center rounded-full bg-zinc-100 text-zinc-900 shadow-lg hover:bg-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-100 focus-visible:ring-offset-2 focus-visible:ring-offset-transparent"
111
+ aria-label="Add Project"
112
+ >
113
+ <svg
114
+ xmlns="http://www.w3.org/2000/svg"
115
+ width="24"
116
+ height="24"
117
+ viewBox="0 0 24 24"
118
+ fill="none"
119
+ stroke="currentColor"
120
+ strokeWidth="2"
121
+ strokeLinecap="round"
122
+ strokeLinejoin="round"
123
+ aria-hidden="true"
124
+ >
125
+ <path d="M5 12h14" />
126
+ <path d="M12 5v14" />
127
+ </svg>
128
+ </button>
129
+
130
+ {/* Add Project Dialog */}
131
+ <AddProjectDialog
132
+ open={isAddDialogOpen}
133
+ onOpenChange={setIsAddDialogOpen}
134
+ onAddProject={handleAddProject}
135
+ />
136
+ </div>
137
+ );
138
+ }
@@ -0,0 +1,138 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import Link from "next/link";
5
+ import dynamic from "next/dynamic";
6
+ import { Settings } from "lucide-react";
7
+ import { ProjectCard } from "@/components/project-card";
8
+ import { AddProjectDialog } from "@/components/add-project-dialog";
9
+ import { useProjects } from "@/hooks/use-projects";
10
+ import { Skeleton } from "@/components/ui/skeleton";
11
+
12
+ // Dynamic import with SSR disabled for WebGL canvas component
13
+ const RippleGrid = dynamic(() => import("@/components/RippleGrid"), { ssr: false });
14
+
15
+ export default function ProjectsPage() {
16
+ const [isAddDialogOpen, setIsAddDialogOpen] = useState(false);
17
+ const { projects, isLoading, error, addProject, updateProjectTags } = useProjects();
18
+
19
+ const handleAddProject = async (input: { name: string; path: string }) => {
20
+ await addProject(input);
21
+ };
22
+
23
+ return (
24
+ <div className="dark relative min-h-screen bg-[#0d0b14]">
25
+ {/* RippleGrid Background - fixed, full-screen, z-0 */}
26
+ <div className="fixed inset-0 z-0 overflow-hidden">
27
+ <RippleGrid
28
+ gridColor="#302a4c"
29
+ rippleIntensity={0.02}
30
+ gridSize={15}
31
+ gridThickness={15}
32
+ fadeDistance={1.8}
33
+ vignetteStrength={0.5}
34
+ glowIntensity={0.5}
35
+ opacity={1}
36
+ gridRotation={0}
37
+ mouseInteractionRadius={0.8}
38
+ mouseInteraction={false}
39
+ enableRainbow={false}
40
+ />
41
+ </div>
42
+
43
+ {/* Settings Icon - fixed top-right, z-20 */}
44
+ <Link
45
+ href="/settings"
46
+ aria-label="Settings"
47
+ className="fixed right-6 top-6 z-20 rounded-md p-2 text-zinc-400 hover:bg-zinc-800/50 hover:text-zinc-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-400 focus-visible:ring-offset-2 focus-visible:ring-offset-transparent"
48
+ >
49
+ <Settings className="h-5 w-5" aria-hidden="true" />
50
+ </Link>
51
+
52
+ {/* Main Content */}
53
+ <main className="relative z-10 flex flex-col items-center px-6 py-16">
54
+ {/* Centered Heading with Space Grotesk */}
55
+ <h1 className="mb-12 text-center text-balance font-heading text-4xl font-bold tracking-tight text-white sm:text-5xl">
56
+ Manage Your Beads Projects
57
+ </h1>
58
+
59
+ <div className="w-full max-w-[1200px]">
60
+ {isLoading ? (
61
+ <div role="status" aria-label="Loading projects" className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
62
+ {[1, 2, 3].map((i) => (
63
+ <div key={i} className="rounded-xl border bg-card/70 p-4 backdrop-blur-md">
64
+ <div className="mb-3 flex gap-1.5">
65
+ <Skeleton className="h-5 w-16" />
66
+ <Skeleton className="h-5 w-12" />
67
+ </div>
68
+ <Skeleton className="h-5 w-40" />
69
+ <Skeleton className="mt-2 h-4 w-48" />
70
+ <Skeleton className="mt-4 h-4 w-32" />
71
+ <Skeleton className="mt-2 h-3 w-28" />
72
+ </div>
73
+ ))}
74
+ </div>
75
+ ) : error ? (
76
+ <div className="rounded-lg border border-red-800/50 bg-red-950/70 p-6 text-center backdrop-blur-md">
77
+ <p className="text-red-400">Error loading projects: {error.message}</p>
78
+ <p className="mt-2 text-sm text-red-500">
79
+ Make sure the Tauri backend is running.
80
+ </p>
81
+ </div>
82
+ ) : projects.length === 0 ? (
83
+ <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
84
+ <div className="rounded-lg border border-dashed border-zinc-700 bg-card/70 p-6 text-center text-zinc-400 backdrop-blur-md">
85
+ <p>No projects yet</p>
86
+ <p className="mt-1 text-sm text-zinc-500">Click the button below to add a project</p>
87
+ </div>
88
+ </div>
89
+ ) : (
90
+ <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
91
+ {projects.map((project) => (
92
+ <ProjectCard
93
+ key={project.id}
94
+ id={project.id}
95
+ name={project.name}
96
+ path={project.path}
97
+ tags={project.tags}
98
+ beadCounts={project.beadCounts}
99
+ onTagsChange={(tags) => updateProjectTags(project.id, tags)}
100
+ />
101
+ ))}
102
+ </div>
103
+ )}
104
+ </div>
105
+ </main>
106
+
107
+ {/* FAB - Add Project, z-20 */}
108
+ <button
109
+ onClick={() => setIsAddDialogOpen(true)}
110
+ className="fixed bottom-6 right-6 z-20 flex h-14 w-14 items-center justify-center rounded-full bg-zinc-100 text-zinc-900 shadow-lg hover:bg-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-100 focus-visible:ring-offset-2 focus-visible:ring-offset-transparent"
111
+ aria-label="Add Project"
112
+ >
113
+ <svg
114
+ xmlns="http://www.w3.org/2000/svg"
115
+ width="24"
116
+ height="24"
117
+ viewBox="0 0 24 24"
118
+ fill="none"
119
+ stroke="currentColor"
120
+ strokeWidth="2"
121
+ strokeLinecap="round"
122
+ strokeLinejoin="round"
123
+ aria-hidden="true"
124
+ >
125
+ <path d="M5 12h14" />
126
+ <path d="M12 5v14" />
127
+ </svg>
128
+ </button>
129
+
130
+ {/* Add Project Dialog */}
131
+ <AddProjectDialog
132
+ open={isAddDialogOpen}
133
+ onOpenChange={setIsAddDialogOpen}
134
+ onAddProject={handleAddProject}
135
+ />
136
+ </div>
137
+ );
138
+ }