heartbeads 0.4.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 (205) hide show
  1. package/.next/BUILD_ID +1 -0
  2. package/.next/app-build-manifest.json +49 -0
  3. package/.next/app-path-routes-manifest.json +1 -0
  4. package/.next/build-manifest.json +32 -0
  5. package/.next/export-marker.json +1 -0
  6. package/.next/images-manifest.json +1 -0
  7. package/.next/next-minimal-server.js.nft.json +1 -0
  8. package/.next/next-server.js.nft.json +1 -0
  9. package/.next/package.json +1 -0
  10. package/.next/prerender-manifest.json +1 -0
  11. package/.next/react-loadable-manifest.json +8 -0
  12. package/.next/required-server-files.json +1 -0
  13. package/.next/routes-manifest.json +1 -0
  14. package/.next/server/app/_not-found/page.js +1 -0
  15. package/.next/server/app/_not-found/page.js.nft.json +1 -0
  16. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -0
  17. package/.next/server/app/_not-found.html +1 -0
  18. package/.next/server/app/_not-found.meta +6 -0
  19. package/.next/server/app/_not-found.rsc +9 -0
  20. package/.next/server/app/api/auth/route.js +1 -0
  21. package/.next/server/app/api/auth/route.js.nft.json +1 -0
  22. package/.next/server/app/api/beads/route.js +8 -0
  23. package/.next/server/app/api/beads/route.js.nft.json +1 -0
  24. package/.next/server/app/api/beads/stream/route.js +10 -0
  25. package/.next/server/app/api/beads/stream/route.js.nft.json +1 -0
  26. package/.next/server/app/api/beads.body +1 -0
  27. package/.next/server/app/api/beads.meta +1 -0
  28. package/.next/server/app/api/config/route.js +8 -0
  29. package/.next/server/app/api/config/route.js.nft.json +1 -0
  30. package/.next/server/app/api/docs/page.js +120 -0
  31. package/.next/server/app/api/docs/page.js.nft.json +1 -0
  32. package/.next/server/app/api/docs/page_client-reference-manifest.js +1 -0
  33. package/.next/server/app/api/docs.html +120 -0
  34. package/.next/server/app/api/docs.meta +5 -0
  35. package/.next/server/app/api/docs.rsc +70 -0
  36. package/.next/server/app/api/login/route.js +1 -0
  37. package/.next/server/app/api/login/route.js.nft.json +1 -0
  38. package/.next/server/app/api/logout/route.js +1 -0
  39. package/.next/server/app/api/logout/route.js.nft.json +1 -0
  40. package/.next/server/app/api/oauth/callback/route.js +1 -0
  41. package/.next/server/app/api/oauth/callback/route.js.nft.json +1 -0
  42. package/.next/server/app/api/oauth/client-metadata.json/route.js +1 -0
  43. package/.next/server/app/api/oauth/client-metadata.json/route.js.nft.json +1 -0
  44. package/.next/server/app/api/oauth/jwks.json/route.js +1 -0
  45. package/.next/server/app/api/oauth/jwks.json/route.js.nft.json +1 -0
  46. package/.next/server/app/api/records/route.js +1 -0
  47. package/.next/server/app/api/records/route.js.nft.json +1 -0
  48. package/.next/server/app/api/status/route.js +1 -0
  49. package/.next/server/app/api/status/route.js.nft.json +1 -0
  50. package/.next/server/app/api/v1/graph/route.js +1 -0
  51. package/.next/server/app/api/v1/graph/route.js.nft.json +1 -0
  52. package/.next/server/app/api/v1/issues/[id]/route.js +1 -0
  53. package/.next/server/app/api/v1/issues/[id]/route.js.nft.json +1 -0
  54. package/.next/server/app/api/v1/ready/route.js +1 -0
  55. package/.next/server/app/api/v1/ready/route.js.nft.json +1 -0
  56. package/.next/server/app/index.html +1 -0
  57. package/.next/server/app/index.meta +5 -0
  58. package/.next/server/app/index.rsc +9 -0
  59. package/.next/server/app/login/page.js +1 -0
  60. package/.next/server/app/login/page.js.nft.json +1 -0
  61. package/.next/server/app/login/page_client-reference-manifest.js +1 -0
  62. package/.next/server/app/login.html +1 -0
  63. package/.next/server/app/login.meta +5 -0
  64. package/.next/server/app/login.rsc +9 -0
  65. package/.next/server/app/opengraph-image.png/route.js +1 -0
  66. package/.next/server/app/opengraph-image.png/route.js.nft.json +1 -0
  67. package/.next/server/app/opengraph-image.png.body +0 -0
  68. package/.next/server/app/opengraph-image.png.meta +1 -0
  69. package/.next/server/app/page.js +24 -0
  70. package/.next/server/app/page.js.nft.json +1 -0
  71. package/.next/server/app/page_client-reference-manifest.js +1 -0
  72. package/.next/server/app/twitter-image.png/route.js +1 -0
  73. package/.next/server/app/twitter-image.png/route.js.nft.json +1 -0
  74. package/.next/server/app/twitter-image.png.body +0 -0
  75. package/.next/server/app/twitter-image.png.meta +1 -0
  76. package/.next/server/app-paths-manifest.json +22 -0
  77. package/.next/server/chunks/247.js +12 -0
  78. package/.next/server/chunks/29.js +1 -0
  79. package/.next/server/chunks/343.js +1 -0
  80. package/.next/server/chunks/460.js +12 -0
  81. package/.next/server/chunks/533.js +38 -0
  82. package/.next/server/chunks/542.js +27 -0
  83. package/.next/server/chunks/590.js +6 -0
  84. package/.next/server/chunks/615.js +15 -0
  85. package/.next/server/chunks/696.js +25 -0
  86. package/.next/server/chunks/719.js +2 -0
  87. package/.next/server/chunks/739.js +1 -0
  88. package/.next/server/chunks/950.js +2 -0
  89. package/.next/server/chunks/font-manifest.json +1 -0
  90. package/.next/server/edge-runtime-webpack.js +2 -0
  91. package/.next/server/edge-runtime-webpack.js.map +1 -0
  92. package/.next/server/font-manifest.json +1 -0
  93. package/.next/server/functions-config-manifest.json +1 -0
  94. package/.next/server/interception-route-rewrite-manifest.js +1 -0
  95. package/.next/server/middleware-build-manifest.js +1 -0
  96. package/.next/server/middleware-manifest.json +32 -0
  97. package/.next/server/middleware-react-loadable-manifest.js +1 -0
  98. package/.next/server/middleware.js +14 -0
  99. package/.next/server/middleware.js.map +1 -0
  100. package/.next/server/next-font-manifest.js +1 -0
  101. package/.next/server/next-font-manifest.json +1 -0
  102. package/.next/server/pages/404.html +1 -0
  103. package/.next/server/pages/500.html +1 -0
  104. package/.next/server/pages/_app.js +1 -0
  105. package/.next/server/pages/_app.js.nft.json +1 -0
  106. package/.next/server/pages/_document.js +1 -0
  107. package/.next/server/pages/_document.js.nft.json +1 -0
  108. package/.next/server/pages/_error.js +1 -0
  109. package/.next/server/pages/_error.js.nft.json +1 -0
  110. package/.next/server/pages-manifest.json +1 -0
  111. package/.next/server/server-reference-manifest.js +1 -0
  112. package/.next/server/server-reference-manifest.json +1 -0
  113. package/.next/server/webpack-runtime.js +1 -0
  114. package/.next/static/chunks/149.a3e3a5dc03e21086.js +1 -0
  115. package/.next/static/chunks/2200cc46-7c93a0e00b0bb825.js +1 -0
  116. package/.next/static/chunks/788-aa413085174e935a.js +1 -0
  117. package/.next/static/chunks/945-3ff1d381a0af1ecd.js +2 -0
  118. package/.next/static/chunks/971-bb44d52bcd9ee2a9.js +1 -0
  119. package/.next/static/chunks/app/_not-found/page-200b7a7a6cfc29df.js +1 -0
  120. package/.next/static/chunks/app/api/docs/page-1dc18f40154cdce6.js +1 -0
  121. package/.next/static/chunks/app/layout-13e3cdaaa416edb6.js +1 -0
  122. package/.next/static/chunks/app/login/page-60d930d64f021753.js +1 -0
  123. package/.next/static/chunks/app/not-found-ae1139bed2018dd8.js +1 -0
  124. package/.next/static/chunks/app/page-583300dd8af66e5a.js +1 -0
  125. package/.next/static/chunks/framework-6e06c675866dc992.js +1 -0
  126. package/.next/static/chunks/main-app-8b0c4a1007dbb7f4.js +1 -0
  127. package/.next/static/chunks/main-e680fb049d7426e1.js +1 -0
  128. package/.next/static/chunks/pages/_app-0c3037849002a4aa.js +1 -0
  129. package/.next/static/chunks/pages/_error-a647cd2c75dc4dc7.js +1 -0
  130. package/.next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  131. package/.next/static/chunks/webpack-117444a4bfe51057.js +1 -0
  132. package/.next/static/css/8c1b520a38ba4ccd.css +3 -0
  133. package/.next/static/vFM69sDrBUf_9ULwPmVAE/_buildManifest.js +1 -0
  134. package/.next/static/vFM69sDrBUf_9ULwPmVAE/_ssgManifest.js +1 -0
  135. package/README.md +389 -0
  136. package/app/api/auth/route.ts +103 -0
  137. package/app/api/beads/route.ts +27 -0
  138. package/app/api/beads/stream/route.ts +83 -0
  139. package/app/api/config/route.ts +48 -0
  140. package/app/api/docs/page.tsx +497 -0
  141. package/app/api/login/route.ts +42 -0
  142. package/app/api/logout/route.ts +14 -0
  143. package/app/api/oauth/callback/route.ts +97 -0
  144. package/app/api/oauth/client-metadata.json/route.ts +33 -0
  145. package/app/api/oauth/jwks.json/route.ts +32 -0
  146. package/app/api/records/route.ts +168 -0
  147. package/app/api/status/route.ts +25 -0
  148. package/app/api/v1/graph/route.ts +251 -0
  149. package/app/api/v1/issues/[id]/route.ts +158 -0
  150. package/app/api/v1/ready/route.ts +229 -0
  151. package/app/globals.css +230 -0
  152. package/app/layout.tsx +51 -0
  153. package/app/login/page.tsx +164 -0
  154. package/app/not-found.tsx +91 -0
  155. package/app/opengraph-image.png +0 -0
  156. package/app/page.tsx +2041 -0
  157. package/app/twitter-image.png +0 -0
  158. package/bin/heartbeads.mjs +225 -0
  159. package/components/ActivityItem.tsx +326 -0
  160. package/components/ActivityOverlay.tsx +125 -0
  161. package/components/ActivityPanel.tsx +345 -0
  162. package/components/AllCommentsPanel.tsx +270 -0
  163. package/components/AuthButton.tsx +202 -0
  164. package/components/BeadTooltip.tsx +246 -0
  165. package/components/BeadsGraph.tsx +2493 -0
  166. package/components/BeadsLogo.tsx +94 -0
  167. package/components/CommentTooltip.tsx +338 -0
  168. package/components/ContextMenu.tsx +272 -0
  169. package/components/DescriptionModal.tsx +595 -0
  170. package/components/GraphStats.tsx +121 -0
  171. package/components/HeartIcon.tsx +33 -0
  172. package/components/HelpPanel.tsx +339 -0
  173. package/components/MobileActionSheet.tsx +255 -0
  174. package/components/NodeDetail.tsx +793 -0
  175. package/components/SettingsModal.tsx +315 -0
  176. package/components/StatusLegend.tsx +99 -0
  177. package/components/TimelineBar.tsx +116 -0
  178. package/components/TutorialOverlay.tsx +235 -0
  179. package/hooks/useBeadsComments.ts +81 -0
  180. package/hooks/useIsMobile.ts +19 -0
  181. package/lib/activity.ts +377 -0
  182. package/lib/agent.ts +29 -0
  183. package/lib/api-helpers.ts +46 -0
  184. package/lib/auth/client.ts +221 -0
  185. package/lib/auth.tsx +159 -0
  186. package/lib/comments.ts +413 -0
  187. package/lib/diff-beads.ts +128 -0
  188. package/lib/discover.ts +228 -0
  189. package/lib/env.ts +33 -0
  190. package/lib/gate.ts +55 -0
  191. package/lib/parse-beads.ts +234 -0
  192. package/lib/session.ts +52 -0
  193. package/lib/settings.ts +42 -0
  194. package/lib/timeline.ts +138 -0
  195. package/lib/tts.ts +397 -0
  196. package/lib/types.ts +271 -0
  197. package/lib/utils.ts +48 -0
  198. package/lib/watch-beads.ts +97 -0
  199. package/next.config.mjs +4 -0
  200. package/package.json +81 -0
  201. package/postcss.config.mjs +9 -0
  202. package/public/image.png +0 -0
  203. package/scripts/generate-jwk.js +38 -0
  204. package/tailwind.config.ts +41 -0
  205. package/tsconfig.json +24 -0
@@ -0,0 +1,121 @@
1
+ "use client";
2
+
3
+ interface GraphStatsProps {
4
+ stats: {
5
+ total: number;
6
+ open: number;
7
+ inProgress: number;
8
+ blocked: number;
9
+ closed: number;
10
+ actionable: number;
11
+ edges: number;
12
+ prefixes: string[];
13
+ };
14
+ }
15
+
16
+ export default function GraphStats({ stats }: GraphStatsProps) {
17
+ return (
18
+ <div className="grid grid-cols-2 gap-2">
19
+ <StatCard
20
+ value={stats.total}
21
+ label="Nodes"
22
+ icon={
23
+ <svg
24
+ className="w-3.5 h-3.5"
25
+ fill="none"
26
+ stroke="currentColor"
27
+ viewBox="0 0 24 24"
28
+ >
29
+ <circle cx="12" cy="12" r="3" strokeWidth={2} />
30
+ </svg>
31
+ }
32
+ />
33
+ <StatCard
34
+ value={stats.edges}
35
+ label="Edges"
36
+ icon={
37
+ <svg
38
+ className="w-3.5 h-3.5"
39
+ fill="none"
40
+ stroke="currentColor"
41
+ viewBox="0 0 24 24"
42
+ >
43
+ <path
44
+ strokeLinecap="round"
45
+ strokeWidth={2}
46
+ d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101"
47
+ />
48
+ </svg>
49
+ }
50
+ />
51
+ <StatCard
52
+ value={stats.actionable}
53
+ label="Actionable"
54
+ color="text-emerald-600"
55
+ icon={
56
+ <svg
57
+ className="w-3.5 h-3.5"
58
+ fill="none"
59
+ stroke="currentColor"
60
+ viewBox="0 0 24 24"
61
+ >
62
+ <path
63
+ strokeLinecap="round"
64
+ strokeLinejoin="round"
65
+ strokeWidth={2}
66
+ d="M13 10V3L4 14h7v7l9-11h-7z"
67
+ />
68
+ </svg>
69
+ }
70
+ />
71
+ <StatCard
72
+ value={stats.open}
73
+ label="Open"
74
+ color="text-emerald-500"
75
+ icon={
76
+ <svg
77
+ className="w-3.5 h-3.5"
78
+ fill="none"
79
+ stroke="currentColor"
80
+ viewBox="0 0 24 24"
81
+ >
82
+ <path
83
+ strokeLinecap="round"
84
+ strokeLinejoin="round"
85
+ strokeWidth={2}
86
+ d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"
87
+ />
88
+ </svg>
89
+ }
90
+ />
91
+ </div>
92
+ );
93
+ }
94
+
95
+ function StatCard({
96
+ value,
97
+ label,
98
+ color,
99
+ icon,
100
+ }: {
101
+ value: number;
102
+ label: string;
103
+ color?: string;
104
+ icon: React.ReactNode;
105
+ }) {
106
+ return (
107
+ <div className="bg-zinc-50 rounded-lg px-3 py-2.5 border border-zinc-100">
108
+ <div className="flex items-center gap-1.5">
109
+ <span className="text-zinc-400">{icon}</span>
110
+ <span
111
+ className={`text-lg font-bold ${color || "text-zinc-700"}`}
112
+ >
113
+ {value}
114
+ </span>
115
+ </div>
116
+ <div className="text-[10px] text-zinc-400 uppercase tracking-wider mt-0.5">
117
+ {label}
118
+ </div>
119
+ </div>
120
+ );
121
+ }
@@ -0,0 +1,33 @@
1
+ // HeartIcon — ported from Hyperscan ReviewSection
2
+ // Shared by NodeDetail and AllCommentsPanel
3
+
4
+ export function HeartIcon({
5
+ className = "w-3 h-3",
6
+ filled = false,
7
+ }: {
8
+ className?: string;
9
+ filled?: boolean;
10
+ }) {
11
+ if (filled) {
12
+ return (
13
+ <svg className={className} viewBox="0 0 24 24" fill="currentColor">
14
+ <path d="M11.645 20.91l-.007-.003-.022-.012a15.247 15.247 0 01-.383-.218 25.18 25.18 0 01-4.244-3.17C4.688 15.36 2.25 12.174 2.25 8.25 2.25 5.322 4.714 3 7.688 3A5.5 5.5 0 0112 5.052 5.5 5.5 0 0116.313 3c2.973 0 5.437 2.322 5.437 5.25 0 3.925-2.438 7.111-4.739 9.256a25.175 25.175 0 01-4.244 3.17 15.247 15.247 0 01-.383.219l-.022.012-.007.004-.003.001a.752.752 0 01-.704 0l-.003-.001z" />
15
+ </svg>
16
+ );
17
+ }
18
+ return (
19
+ <svg
20
+ className={className}
21
+ fill="none"
22
+ viewBox="0 0 24 24"
23
+ strokeWidth={1.5}
24
+ stroke="currentColor"
25
+ >
26
+ <path
27
+ strokeLinecap="round"
28
+ strokeLinejoin="round"
29
+ d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12z"
30
+ />
31
+ </svg>
32
+ );
33
+ }
@@ -0,0 +1,339 @@
1
+ "use client";
2
+
3
+ import { TUTORIAL_STEPS } from "./TutorialOverlay";
4
+
5
+ // Catppuccin Latte accents
6
+ const CAT = {
7
+ red: "#d20f39",
8
+ teal: "#179299",
9
+ peach: "#fe640b",
10
+ blue: "#1e66f5",
11
+ green: "#40a02b",
12
+ mauve: "#8839ef",
13
+ sapphire: "#209fb5",
14
+ pink: "#ea76cb",
15
+ surface: "#dce0e8",
16
+ };
17
+
18
+ interface HelpPanelProps {
19
+ isOpen: boolean;
20
+ onClose: () => void;
21
+ tutorialStep?: number | null;
22
+ onStartTutorial?: () => void;
23
+ onNextStep?: () => void;
24
+ onPrevStep?: () => void;
25
+ onEndTutorial?: () => void;
26
+ }
27
+
28
+ export function HelpPanel({
29
+ isOpen,
30
+ onClose,
31
+ tutorialStep = null,
32
+ onStartTutorial,
33
+ onNextStep,
34
+ onPrevStep,
35
+ onEndTutorial,
36
+ }: HelpPanelProps) {
37
+ const isTutorialActive = tutorialStep !== null;
38
+ const headerText = isTutorialActive ? "Tutorial" : "Welcome to Heartbeads";
39
+
40
+ const content = isTutorialActive ? (
41
+ <TutorialContent
42
+ step={tutorialStep}
43
+ onNext={onNextStep!}
44
+ onPrev={onPrevStep!}
45
+ onEnd={onEndTutorial!}
46
+ />
47
+ ) : (
48
+ <HelpContent onStartTutorial={onStartTutorial} />
49
+ );
50
+
51
+ return (
52
+ <>
53
+ {/* Desktop: right sidebar — z-[60] during tutorial so it sits above the overlay */}
54
+ <aside
55
+ className={`hidden md:flex absolute top-0 right-0 h-full w-[360px] bg-white border-l border-zinc-200 flex-col shadow-xl transform transition-transform duration-300 ease-out ${
56
+ isOpen ? "translate-x-0" : "translate-x-full"
57
+ } ${isTutorialActive ? "z-[60]" : "z-30"}`}
58
+ >
59
+ <div className="flex items-center justify-between px-5 py-3 border-b border-zinc-100 shrink-0">
60
+ <h2 className="text-sm font-semibold text-zinc-900">
61
+ {headerText}
62
+ </h2>
63
+ <button
64
+ onClick={onClose}
65
+ className="p-1 text-zinc-400 hover:text-zinc-600 transition-colors"
66
+ >
67
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2}>
68
+ <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
69
+ </svg>
70
+ </button>
71
+ </div>
72
+ <div className="flex-1 overflow-y-auto custom-scrollbar">
73
+ {content}
74
+ </div>
75
+ </aside>
76
+
77
+ {/* Mobile: bottom drawer */}
78
+ <div
79
+ className={`md:hidden fixed inset-x-0 bottom-0 transform transition-transform duration-300 ease-out ${
80
+ isOpen ? "translate-y-0" : "translate-y-full"
81
+ } ${isTutorialActive ? "z-[60]" : "z-20"}`}
82
+ >
83
+ <div className="bg-white rounded-t-2xl shadow-2xl border-t border-zinc-200 max-h-[70vh] flex flex-col">
84
+ <div className="flex items-center justify-between px-5 py-3 border-b border-zinc-100 shrink-0">
85
+ <h2 className="text-sm font-semibold text-zinc-900">
86
+ {headerText}
87
+ </h2>
88
+ <button
89
+ onClick={onClose}
90
+ className="p-1 text-zinc-400 hover:text-zinc-600 transition-colors"
91
+ >
92
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2}>
93
+ <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
94
+ </svg>
95
+ </button>
96
+ </div>
97
+ <div className="flex-1 overflow-y-auto custom-scrollbar">
98
+ {content}
99
+ </div>
100
+ </div>
101
+ </div>
102
+ </>
103
+ );
104
+ }
105
+
106
+ /* ------------------------------------------------------------------ */
107
+ /* Tutorial step-by-step content */
108
+ /* ------------------------------------------------------------------ */
109
+
110
+ function TutorialContent({
111
+ step,
112
+ onNext,
113
+ onPrev,
114
+ onEnd,
115
+ }: {
116
+ step: number;
117
+ onNext: () => void;
118
+ onPrev: () => void;
119
+ onEnd: () => void;
120
+ }) {
121
+ const currentStep = TUTORIAL_STEPS[step];
122
+ const totalSteps = TUTORIAL_STEPS.length;
123
+ const isFirst = step === 0;
124
+ const isLast = step === totalSteps - 1;
125
+
126
+ return (
127
+ <div className="px-5 py-4 flex flex-col min-h-[300px]">
128
+ {/* Step indicator */}
129
+ <div className="flex items-center justify-between mb-4">
130
+ <span className="text-xs font-medium" style={{ color: CAT.teal }}>
131
+ {step + 1} / {totalSteps}
132
+ </span>
133
+ <div className="flex gap-1">
134
+ {Array.from({ length: totalSteps }).map((_, i) => (
135
+ <div
136
+ key={i}
137
+ className="w-1.5 h-1.5 rounded-full transition-colors"
138
+ style={{
139
+ backgroundColor:
140
+ i === step ? CAT.green : i < step ? CAT.teal : CAT.surface,
141
+ }}
142
+ />
143
+ ))}
144
+ </div>
145
+ </div>
146
+
147
+ {/* Step title */}
148
+ <h3 className="text-base font-semibold text-zinc-900 mb-2">
149
+ {currentStep.title}
150
+ </h3>
151
+
152
+ {/* Step description */}
153
+ <p className="text-[13px] text-zinc-600 leading-relaxed mb-6">
154
+ {currentStep.description}
155
+ </p>
156
+
157
+ {/* Spacer */}
158
+ <div className="flex-1" />
159
+
160
+ {/* Navigation */}
161
+ <div className="flex items-center gap-2 pt-4 border-t border-zinc-100">
162
+ {isFirst ? (
163
+ <button
164
+ onClick={onEnd}
165
+ className="px-4 py-2 text-sm font-medium text-zinc-400 hover:text-zinc-600 rounded-lg hover:bg-zinc-50 transition-colors"
166
+ >
167
+ Skip
168
+ </button>
169
+ ) : (
170
+ <button
171
+ onClick={onPrev}
172
+ className="px-4 py-2 text-sm font-medium rounded-lg hover:bg-zinc-50 transition-colors"
173
+ style={{ color: CAT.blue }}
174
+ >
175
+ {"Back"}
176
+ </button>
177
+ )}
178
+ <div className="flex-1" />
179
+ {isLast ? (
180
+ <button
181
+ onClick={onEnd}
182
+ className="px-4 py-2 text-sm font-medium text-white rounded-lg bg-emerald-500 hover:bg-emerald-600 transition-colors"
183
+ >
184
+ {"Done"}
185
+ </button>
186
+ ) : (
187
+ <button
188
+ onClick={onNext}
189
+ className="px-4 py-2 text-sm font-medium text-white rounded-lg bg-emerald-500 hover:bg-emerald-600 transition-colors"
190
+ >
191
+ {"Next"}
192
+ </button>
193
+ )}
194
+ </div>
195
+ </div>
196
+ );
197
+ }
198
+
199
+ /* ------------------------------------------------------------------ */
200
+ /* Bullet with Catppuccin colored dot */
201
+ /* ------------------------------------------------------------------ */
202
+
203
+ function Bullet({ color, children }: { color: string; children: React.ReactNode }) {
204
+ return (
205
+ <li className="flex gap-2.5 items-start">
206
+ <span
207
+ className="w-1.5 h-1.5 rounded-full mt-[6px] shrink-0"
208
+ style={{ backgroundColor: color }}
209
+ />
210
+ <span>{children}</span>
211
+ </li>
212
+ );
213
+ }
214
+
215
+ function SectionTitle({ children, color }: { children: React.ReactNode; color: string }) {
216
+ return (
217
+ <h3
218
+ className="text-[11px] font-semibold uppercase tracking-widest mb-2 mt-5 first:mt-0"
219
+ style={{ color }}
220
+ >
221
+ {children}
222
+ </h3>
223
+ );
224
+ }
225
+
226
+ /* ------------------------------------------------------------------ */
227
+ /* Static help content */
228
+ /* ------------------------------------------------------------------ */
229
+
230
+ function HelpContent({ onStartTutorial }: { onStartTutorial?: () => void }) {
231
+ return (
232
+ <div className="px-5 py-4 text-[13px] text-zinc-600 leading-relaxed">
233
+ {/* Start Tutorial */}
234
+ {onStartTutorial && (
235
+ <button
236
+ onClick={onStartTutorial}
237
+ className="w-full flex items-center justify-center gap-2 px-4 py-2.5 mb-5 text-white text-sm font-medium rounded-lg bg-emerald-500 hover:bg-emerald-600 transition-colors"
238
+ >
239
+ <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
240
+ <path strokeLinecap="round" strokeLinejoin="round" d="M12 18v-5.25m0 0a6.01 6.01 0 001.5-.189m-1.5.189a6.01 6.01 0 01-1.5-.189m3.75 7.478a12.06 12.06 0 01-4.5 0m3.75 2.383a14.406 14.406 0 01-3 0M14.25 18v-.192c0-.983.658-1.823 1.508-2.316a7.5 7.5 0 10-7.517 0c.85.493 1.509 1.333 1.509 2.316V18" />
241
+ </svg>
242
+ Take the guided tour
243
+ </button>
244
+ )}
245
+
246
+ <p className="text-zinc-900 font-medium mb-1">
247
+ Your command center for AI coding tasks.
248
+ </p>
249
+ <p className="mb-4 text-zinc-500">
250
+ Heartbeads shows everything your AI agents are working on as a
251
+ live, interactive graph &mdash; tasks, dependencies, who&apos;s doing what, and
252
+ what&apos;s blocking progress.
253
+ </p>
254
+
255
+ <SectionTitle color={CAT.red}>The graph</SectionTitle>
256
+ <p className="mb-2">
257
+ Each <strong>circle</strong> is a task. <strong>Flowing particles</strong>{" "}
258
+ stream between them to show dependency direction.
259
+ </p>
260
+ <ul className="space-y-1.5 mb-3">
261
+ <Bullet color={CAT.red}><strong>Bigger circles</strong> &mdash; more connected, more important</Bullet>
262
+ <Bullet color={CAT.red}><strong>Solid lines + particles</strong> &mdash; &ldquo;blocks&rdquo; (A must finish before B)</Bullet>
263
+ <Bullet color={CAT.red}><strong>Dashed lines</strong> &mdash; parent-child grouping</Bullet>
264
+ <Bullet color={CAT.red}><strong>Colored ring</strong> &mdash; which project it belongs to</Bullet>
265
+ </ul>
266
+
267
+ <SectionTitle color={CAT.blue}>Navigation</SectionTitle>
268
+ <ul className="space-y-1.5 mb-3">
269
+ <Bullet color={CAT.blue}><strong>Click</strong> a node to open its details</Bullet>
270
+ <Bullet color={CAT.blue}><strong>Hover</strong> for a quick summary</Bullet>
271
+ <Bullet color={CAT.blue}><strong>Right-click</strong> for actions &mdash; descriptions, comments, claims, collapse, or focus on an epic</Bullet>
272
+ <Bullet color={CAT.blue}><strong>Scroll</strong> to zoom, <strong>drag</strong> to pan</Bullet>
273
+ <Bullet color={CAT.blue}><strong>Cmd/Ctrl+F</strong> to search by name, ID, owner, or commenter</Bullet>
274
+ </ul>
275
+
276
+ <SectionTitle color={CAT.teal}>Layouts</SectionTitle>
277
+ <p className="mb-2">
278
+ Top-left buttons rearrange the graph:
279
+ </p>
280
+ <ul className="space-y-1.5 mb-3">
281
+ <Bullet color={CAT.teal}><strong>Force</strong> &mdash; organic, physics-based</Bullet>
282
+ <Bullet color={CAT.teal}><strong>DAG</strong> &mdash; clean top-down tree</Bullet>
283
+ <Bullet color={CAT.teal}><strong>Radial</strong> &mdash; rings from center outward</Bullet>
284
+ <Bullet color={CAT.teal}><strong>Cluster</strong> &mdash; grouped by project</Bullet>
285
+ <Bullet color={CAT.teal}><strong>Spread</strong> &mdash; spaced out for screenshots</Bullet>
286
+ </ul>
287
+
288
+ <SectionTitle color={CAT.peach}>Color modes</SectionTitle>
289
+ <p className="mb-2">
290
+ Bottom-right panel &mdash; paint nodes by:
291
+ </p>
292
+ <ul className="space-y-1.5 mb-3">
293
+ <Bullet color={CAT.peach}><strong>Status</strong> &mdash; open, in progress, blocked, closed</Bullet>
294
+ <Bullet color={CAT.peach}><strong>Priority</strong> &mdash; P0 critical to P4 backlog</Bullet>
295
+ <Bullet color={CAT.peach}><strong>Owner</strong> &mdash; who created it</Bullet>
296
+ <Bullet color={CAT.peach}><strong>Assignee</strong> &mdash; who&apos;s working on it</Bullet>
297
+ <Bullet color={CAT.peach}><strong>Prefix</strong> &mdash; which project</Bullet>
298
+ </ul>
299
+
300
+ <SectionTitle color={CAT.mauve}>More</SectionTitle>
301
+ <ul className="space-y-1.5 mb-3">
302
+ <Bullet color={CAT.mauve}><strong>Collapse/Expand</strong> &mdash; tidy up epics into single nodes</Bullet>
303
+ <Bullet color={CAT.mauve}><strong>Focus on epic</strong> &mdash; right-click an epic to isolate its subgraph (children + dependencies). Click the banner or right-click again to return</Bullet>
304
+ <Bullet color={CAT.mauve}><strong>Clusters</strong> &mdash; dashed circles grouping projects when zoomed out</Bullet>
305
+ <Bullet color={CAT.mauve}><strong>Replay</strong> &mdash; step through your project&apos;s history</Bullet>
306
+ <Bullet color={CAT.mauve}><strong>Comments</strong> &mdash; leave notes on tasks (sign in first)</Bullet>
307
+ <Bullet color={CAT.mauve}><strong>Activity feed</strong> &mdash; real-time feed filtered to only your local beads, not global</Bullet>
308
+ <Bullet color={CAT.mauve}><strong>Claim tasks</strong> &mdash; right-click to mark as yours</Bullet>
309
+ <Bullet color={CAT.mauve}><strong>Minimap</strong> &mdash; click to jump, drag edges to resize</Bullet>
310
+ <Bullet color={CAT.mauve}><strong>Auto-fit</strong> &mdash; top-left toggle to lock/unlock automatic camera reframing</Bullet>
311
+ <Bullet color={CAT.mauve}><strong>Pulse</strong> &mdash; the most recently active node pulses with emerald ripples. Toggle it on/off in view controls</Bullet>
312
+ <Bullet color={CAT.mauve}><strong>Copy</strong> &mdash; clipboard icon copies task with project info</Bullet>
313
+ </ul>
314
+
315
+ <div className="mt-5 pt-4 border-t border-zinc-100 text-xs text-zinc-400">
316
+ Built with{" "}
317
+ <a
318
+ href="https://github.com/GainForest/beads"
319
+ target="_blank"
320
+ rel="noopener noreferrer"
321
+ className="underline underline-offset-2 transition-colors"
322
+ style={{ color: CAT.green }}
323
+ >
324
+ beads
325
+ </a>{" "}
326
+ &mdash; open-source issue tracking for AI-native development. Heartbeads is built for the GainForest agentic workflow and open-sourced at{" "}
327
+ <a
328
+ href="https://github.com/daviddao/heartbeads"
329
+ target="_blank"
330
+ rel="noopener noreferrer"
331
+ className="underline underline-offset-2 transition-colors"
332
+ style={{ color: CAT.green }}
333
+ >
334
+ github.com/daviddao/heartbeads
335
+ </a>.
336
+ </div>
337
+ </div>
338
+ );
339
+ }