heartbeads 0.6.1 → 0.6.2
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.
- package/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +2 -2
- package/.next/required-server-files.json +1 -1
- package/.next/server/app/_global-error.html +2 -2
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +1 -1
- package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/api/beads/route.js.nft.json +1 -1
- package/.next/server/app/api/beads/stream/route.js.nft.json +1 -1
- package/.next/server/app/api/config/route.js.nft.json +1 -1
- package/.next/server/app/api/docs.html +2 -2
- package/.next/server/app/api/docs.rsc +1 -1
- package/.next/server/app/api/docs.segments/_full.segment.rsc +1 -1
- package/.next/server/app/api/docs.segments/_head.segment.rsc +1 -1
- package/.next/server/app/api/docs.segments/_index.segment.rsc +1 -1
- package/.next/server/app/api/docs.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/api/docs.segments/api/docs/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/api/docs.segments/api/docs.segment.rsc +1 -1
- package/.next/server/app/api/docs.segments/api.segment.rsc +1 -1
- package/.next/server/app/api/v1/graph/route.js.nft.json +1 -1
- package/.next/server/app/api/v1/issues/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/v1/ready/route.js.nft.json +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +2 -2
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/index.segments/_full.segment.rsc +2 -2
- package/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/login.html +1 -1
- package/.next/server/app/login.rsc +1 -1
- package/.next/server/app/login.segments/_full.segment.rsc +1 -1
- package/.next/server/app/login.segments/_head.segment.rsc +1 -1
- package/.next/server/app/login.segments/_index.segment.rsc +1 -1
- package/.next/server/app/login.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/login.segments/login.segment.rsc +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/chunks/48cc8_next_dist_esm_build_templates_app-route_1d3b72ae.js +1 -1
- package/.next/server/chunks/48cc8_next_dist_esm_build_templates_app-route_1d3b72ae.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__2d60806f._.js +2 -2
- package/.next/server/chunks/[root-of-the-server]__2d60806f._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__3cb3ed30._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__3cb3ed30._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__6125c4e2._.js +2 -2
- package/.next/server/chunks/[root-of-the-server]__6125c4e2._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__67c802f8._.js +2 -2
- package/.next/server/chunks/[root-of-the-server]__67c802f8._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__77e35ba8._.js +2 -2
- package/.next/server/chunks/[root-of-the-server]__77e35ba8._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__8261fec1._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__8261fec1._.js.map +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__04e6e565._.js +2 -2
- package/.next/server/chunks/ssr/[root-of-the-server]__04e6e565._.js.map +1 -1
- package/.next/server/edge/chunks/48cc8_next_dist_5a0b8a90._.js +1 -1
- package/.next/server/edge/chunks/48cc8_next_dist_5a0b8a90._.js.map +1 -1
- package/.next/server/middleware/middleware-manifest.json +3 -3
- package/.next/server/middleware-manifest.json +3 -3
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +2 -2
- package/.next/static/chunks/7e4fcecd4ca504d5.js +24 -0
- package/.next/static/zq8Ew2hci2YIJ61-i832n/_clientMiddlewareManifest.json +6 -0
- package/app/api/login/route.ts +15 -0
- package/app/page.tsx +46 -35
- package/components/BeadsGraph.tsx +15 -1
- package/components/NodeDetail.tsx +15 -7
- package/lib/activity.ts +22 -2
- package/lib/parse-beads.ts +24 -10
- package/lib/types.ts +82 -1
- package/package.json +1 -1
- package/.next/static/bc2EgBqgYtYOdWusC2URO/_clientMiddlewareManifest.json +0 -6
- package/.next/static/chunks/6cdcba7fb18f6f79.js +0 -24
- /package/.next/static/{bc2EgBqgYtYOdWusC2URO → zq8Ew2hci2YIJ61-i832n}/_buildManifest.js +0 -0
- /package/.next/static/{bc2EgBqgYtYOdWusC2URO → zq8Ew2hci2YIJ61-i832n}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"regexp": "^(?:\\/(_next\\/data\\/[^/]{1,}))?(?:\\/((?!login|api\\/auth|api\\/oauth|_next\\/static|_next\\/image|favicon\\.ico|image\\.png|opengraph-image\\.png|twitter-image\\.png).*))(\\\\.json)?[\\/#\\?]?$",
|
|
4
|
+
"originalSource": "/((?!login|api/auth|api/oauth|_next/static|_next/image|favicon\\.ico|image\\.png|opengraph-image\\.png|twitter-image\\.png).*)"
|
|
5
|
+
}
|
|
6
|
+
]
|
package/app/api/login/route.ts
CHANGED
|
@@ -30,7 +30,22 @@ export async function POST(request: NextRequest) {
|
|
|
30
30
|
|
|
31
31
|
return NextResponse.json({ redirectUrl: url.toString() });
|
|
32
32
|
} catch (error) {
|
|
33
|
+
// Enhanced error logging for debugging
|
|
34
|
+
const errorObj = error as any;
|
|
33
35
|
console.error("OAuth authorize failed:", error);
|
|
36
|
+
if (errorObj?.message) {
|
|
37
|
+
console.error("Error message:", errorObj.message);
|
|
38
|
+
}
|
|
39
|
+
if (errorObj?.cause) {
|
|
40
|
+
console.error("Error cause:", errorObj.cause);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Check if error suggests OAuth endpoints are blocked by password middleware
|
|
44
|
+
const errorStr = errorObj?.message || String(error);
|
|
45
|
+
if (errorStr.includes('401') || errorStr.includes('Unauthorized') || errorStr.includes('client_metadata')) {
|
|
46
|
+
console.error("HINT: OAuth endpoints may be blocked by password middleware. Ensure /api/oauth/* routes are publicly accessible.");
|
|
47
|
+
}
|
|
48
|
+
|
|
34
49
|
let errorMessage = "Couldn't initiate login";
|
|
35
50
|
|
|
36
51
|
if (error instanceof OAuthResolverError) {
|
package/app/page.tsx
CHANGED
|
@@ -434,8 +434,9 @@ export default function Home() {
|
|
|
434
434
|
// Count per-person stats from nodes
|
|
435
435
|
for (const node of nodes) {
|
|
436
436
|
// Created: credit the creator
|
|
437
|
-
|
|
438
|
-
|
|
437
|
+
const creator = node.createdBy || node.owner || node.assignee;
|
|
438
|
+
if (creator) {
|
|
439
|
+
const e = getOrCreate(creator);
|
|
439
440
|
if (e) e.createdCount++;
|
|
440
441
|
}
|
|
441
442
|
|
|
@@ -987,8 +988,9 @@ export default function Home() {
|
|
|
987
988
|
|
|
988
989
|
// Count per-person stats from timeline-filtered nodes
|
|
989
990
|
for (const node of nodes) {
|
|
990
|
-
|
|
991
|
-
|
|
991
|
+
const creator = node.createdBy || node.owner || node.assignee;
|
|
992
|
+
if (creator) {
|
|
993
|
+
const e = getOrCreate(creator);
|
|
992
994
|
if (e) e.createdCount++;
|
|
993
995
|
}
|
|
994
996
|
if (node.status === "closed" && node.assignee) {
|
|
@@ -1713,6 +1715,29 @@ export default function Home() {
|
|
|
1713
1715
|
[]
|
|
1714
1716
|
);
|
|
1715
1717
|
|
|
1718
|
+
// Helper function to find the node ID where a contributor's avatar is displayed
|
|
1719
|
+
const findAvatarNodeId = useCallback(
|
|
1720
|
+
(handle: string): string | null => {
|
|
1721
|
+
// 1. Check claimed nodes first (claims take visual priority)
|
|
1722
|
+
for (const [nodeId, claim] of claimedNodeAvatars) {
|
|
1723
|
+
if (claim.handle === handle) return nodeId;
|
|
1724
|
+
}
|
|
1725
|
+
// 2. Fall back to assignee avatar node
|
|
1726
|
+
for (const [nodeId, info] of assigneeNodeAvatarsResolved) {
|
|
1727
|
+
if (info.handle === handle) return nodeId;
|
|
1728
|
+
}
|
|
1729
|
+
// 3. Last resort: any assigned open node, most recently updated
|
|
1730
|
+
if (data) {
|
|
1731
|
+
const fallback = data.graphData.nodes
|
|
1732
|
+
.filter((n) => n.assignee === handle && n.status !== "closed")
|
|
1733
|
+
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
1734
|
+
if (fallback.length > 0) return fallback[0].id;
|
|
1735
|
+
}
|
|
1736
|
+
return null;
|
|
1737
|
+
},
|
|
1738
|
+
[claimedNodeAvatars, assigneeNodeAvatarsResolved, data]
|
|
1739
|
+
);
|
|
1740
|
+
|
|
1716
1741
|
// Keyboard shortcut: Ctrl/Cmd+F to open search, Escape to close, Shift+0 to focus most recent node
|
|
1717
1742
|
useEffect(() => {
|
|
1718
1743
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
@@ -1755,44 +1780,16 @@ export default function Home() {
|
|
|
1755
1780
|
const entry = timelineFilteredLeaderboard[rank - 1];
|
|
1756
1781
|
if (!entry || !data) return;
|
|
1757
1782
|
|
|
1758
|
-
|
|
1759
|
-
let targetNodeId: string | null = null;
|
|
1760
|
-
|
|
1761
|
-
// 1. Check claimed nodes first (claims take visual priority)
|
|
1762
|
-
for (const [nodeId, claim] of claimedNodeAvatars) {
|
|
1763
|
-
if (claim.handle === entry.handle) {
|
|
1764
|
-
targetNodeId = nodeId;
|
|
1765
|
-
break;
|
|
1766
|
-
}
|
|
1767
|
-
}
|
|
1768
|
-
|
|
1769
|
-
// 2. Fall back to assignee avatar node
|
|
1770
|
-
if (!targetNodeId) {
|
|
1771
|
-
for (const [nodeId, info] of assigneeNodeAvatarsResolved) {
|
|
1772
|
-
if (info.handle === entry.handle) {
|
|
1773
|
-
targetNodeId = nodeId;
|
|
1774
|
-
break;
|
|
1775
|
-
}
|
|
1776
|
-
}
|
|
1777
|
-
}
|
|
1778
|
-
|
|
1779
|
-
// 3. Last resort: any assigned open node (in case avatar maps haven't populated yet)
|
|
1780
|
-
if (!targetNodeId) {
|
|
1781
|
-
const fallback = data.graphData.nodes
|
|
1782
|
-
.filter(n => n.assignee === entry.handle && n.status !== 'closed')
|
|
1783
|
-
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
1784
|
-
if (fallback.length > 0) targetNodeId = fallback[0].id;
|
|
1785
|
-
}
|
|
1786
|
-
|
|
1783
|
+
const targetNodeId = findAvatarNodeId(entry.handle);
|
|
1787
1784
|
if (targetNodeId) {
|
|
1788
|
-
const node = data.graphData.nodes.find(n => n.id === targetNodeId);
|
|
1785
|
+
const node = data.graphData.nodes.find((n) => n.id === targetNodeId);
|
|
1789
1786
|
if (node) graphRef.current?.focusNode(node);
|
|
1790
1787
|
}
|
|
1791
1788
|
}
|
|
1792
1789
|
};
|
|
1793
1790
|
window.addEventListener("keydown", handleKeyDown);
|
|
1794
1791
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
1795
|
-
}, [searchOpen, timelineFilteredActivity, data, timelineFilteredLeaderboard,
|
|
1792
|
+
}, [searchOpen, timelineFilteredActivity, data, timelineFilteredLeaderboard, findAvatarNodeId]);
|
|
1796
1793
|
|
|
1797
1794
|
// Handle search result selection via keyboard
|
|
1798
1795
|
const handleSearchKeyDown = useCallback(
|
|
@@ -2523,6 +2520,9 @@ export default function Home() {
|
|
|
2523
2520
|
setActivityPanelOpen(false);
|
|
2524
2521
|
setHelpPanelOpen(false);
|
|
2525
2522
|
setLeaderboardPanelOpen(false);
|
|
2523
|
+
// Zoom to the avatar node
|
|
2524
|
+
const nodeId = findAvatarNodeId(handle);
|
|
2525
|
+
if (nodeId) graphRef.current?.zoomToNode(nodeId);
|
|
2526
2526
|
}}
|
|
2527
2527
|
/>
|
|
2528
2528
|
</div>
|
|
@@ -2834,6 +2834,10 @@ export default function Home() {
|
|
|
2834
2834
|
currentDid={session?.did}
|
|
2835
2835
|
repoUrls={repoUrls}
|
|
2836
2836
|
onOpenSettings={() => setSettingsModalOpen(true)}
|
|
2837
|
+
onProfileClick={(handle) => {
|
|
2838
|
+
setProfilePanelHandle(handle);
|
|
2839
|
+
setSelectedNode(null);
|
|
2840
|
+
}}
|
|
2837
2841
|
/>
|
|
2838
2842
|
|
|
2839
2843
|
</div>
|
|
@@ -2893,6 +2897,10 @@ export default function Home() {
|
|
|
2893
2897
|
currentDid={session?.did}
|
|
2894
2898
|
repoUrls={repoUrls}
|
|
2895
2899
|
onOpenSettings={() => setSettingsModalOpen(true)}
|
|
2900
|
+
onProfileClick={(handle) => {
|
|
2901
|
+
setProfilePanelHandle(handle);
|
|
2902
|
+
setSelectedNode(null);
|
|
2903
|
+
}}
|
|
2896
2904
|
/>
|
|
2897
2905
|
</div>
|
|
2898
2906
|
</div>
|
|
@@ -2950,6 +2958,9 @@ export default function Home() {
|
|
|
2950
2958
|
setAllCommentsPanelOpen(false);
|
|
2951
2959
|
setActivityPanelOpen(false);
|
|
2952
2960
|
setHelpPanelOpen(false);
|
|
2961
|
+
// Zoom to the avatar node
|
|
2962
|
+
const nodeId = findAvatarNodeId(handle);
|
|
2963
|
+
if (nodeId) graphRef.current?.zoomToNode(nodeId);
|
|
2953
2964
|
}}
|
|
2954
2965
|
/>
|
|
2955
2966
|
|
|
@@ -25,6 +25,7 @@ let _ForceGraph2DModule: React.ComponentType<any> | null = null;
|
|
|
25
25
|
type LayoutMode = "force" | "dag" | "radial" | "cluster" | "spread";
|
|
26
26
|
export interface BeadsGraphHandle {
|
|
27
27
|
focusNode: (node: GraphNode) => void;
|
|
28
|
+
zoomToNode: (nodeId: string) => void;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
interface BeadsGraphProps {
|
|
@@ -1064,8 +1065,21 @@ const BeadsGraph = forwardRef<BeadsGraphHandle, BeadsGraphProps>(function BeadsG
|
|
|
1064
1065
|
[onNodeClick, viewNodes]
|
|
1065
1066
|
);
|
|
1066
1067
|
|
|
1068
|
+
// Zoom to node without selecting it (for profile panel navigation)
|
|
1069
|
+
const zoomToNode = useCallback(
|
|
1070
|
+
(nodeId: string) => {
|
|
1071
|
+
const fg = graphRef.current;
|
|
1072
|
+
if (!fg) return;
|
|
1073
|
+
const graphNode = viewNodes.find((n) => n.id === nodeId);
|
|
1074
|
+
if (!graphNode || graphNode.x === undefined || graphNode.y === undefined) return;
|
|
1075
|
+
fg.centerAt(graphNode.x, graphNode.y, 500);
|
|
1076
|
+
fg.zoom(2.5, 500);
|
|
1077
|
+
},
|
|
1078
|
+
[viewNodes]
|
|
1079
|
+
);
|
|
1080
|
+
|
|
1067
1081
|
// Expose focusNode to parent via ref
|
|
1068
|
-
useImperativeHandle(ref, () => ({ focusNode }), [focusNode]);
|
|
1082
|
+
useImperativeHandle(ref, () => ({ focusNode, zoomToNode }), [focusNode, zoomToNode]);
|
|
1069
1083
|
|
|
1070
1084
|
// ForceGraph2D event handlers (extracted from inline lambdas for stable references)
|
|
1071
1085
|
const handleForceGraphNodeHover = useCallback((node: any) => {
|
|
@@ -55,6 +55,7 @@ interface NodeDetailProps {
|
|
|
55
55
|
currentDid?: string;
|
|
56
56
|
repoUrls?: Record<string, string>;
|
|
57
57
|
onOpenSettings?: () => void;
|
|
58
|
+
onProfileClick?: (handle: string) => void;
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
export default function NodeDetail({
|
|
@@ -70,6 +71,7 @@ export default function NodeDetail({
|
|
|
70
71
|
currentDid,
|
|
71
72
|
repoUrls,
|
|
72
73
|
onOpenSettings,
|
|
74
|
+
onProfileClick,
|
|
73
75
|
}: NodeDetailProps) {
|
|
74
76
|
// Reply state — managed here so it's shared across the comment tree
|
|
75
77
|
const [replyingToUri, setReplyingToUri] = useState<string | null>(null);
|
|
@@ -338,20 +340,26 @@ export default function NodeDetail({
|
|
|
338
340
|
</span>
|
|
339
341
|
</div>
|
|
340
342
|
)}
|
|
341
|
-
{node.owner && (
|
|
343
|
+
{(node.createdBy || node.owner || node.assignee) && (
|
|
342
344
|
<div className="flex justify-between">
|
|
343
|
-
<span>
|
|
344
|
-
<
|
|
345
|
-
{node.owner}
|
|
346
|
-
|
|
345
|
+
<span>Created by</span>
|
|
346
|
+
<button
|
|
347
|
+
onClick={() => onProfileClick?.(node.createdBy || node.owner || node.assignee || "")}
|
|
348
|
+
className="text-zinc-700 dark:text-zinc-300 font-medium truncate ml-2 hover:text-emerald-600 dark:hover:text-emerald-400 transition-colors"
|
|
349
|
+
>
|
|
350
|
+
{node.createdBy || node.owner || node.assignee}
|
|
351
|
+
</button>
|
|
347
352
|
</div>
|
|
348
353
|
)}
|
|
349
354
|
<div className="flex justify-between">
|
|
350
355
|
<span>Assignee</span>
|
|
351
356
|
{node.assignee ? (
|
|
352
|
-
<
|
|
357
|
+
<button
|
|
358
|
+
onClick={() => onProfileClick?.(node.assignee!)}
|
|
359
|
+
className="text-zinc-700 dark:text-zinc-300 font-medium truncate ml-2 hover:text-emerald-600 dark:hover:text-emerald-400 transition-colors"
|
|
360
|
+
>
|
|
353
361
|
{node.assignee}
|
|
354
|
-
</
|
|
362
|
+
</button>
|
|
355
363
|
) : (
|
|
356
364
|
<span className="text-zinc-400 dark:text-zinc-500 italic ml-2">
|
|
357
365
|
No assignee
|
package/lib/activity.ts
CHANGED
|
@@ -123,7 +123,13 @@ export function buildHistoricalFeed(
|
|
|
123
123
|
time,
|
|
124
124
|
nodeId: node.id,
|
|
125
125
|
nodeTitle: node.title,
|
|
126
|
-
actor: node.createdBy
|
|
126
|
+
actor: node.createdBy
|
|
127
|
+
? { handle: node.createdBy }
|
|
128
|
+
: node.owner
|
|
129
|
+
? { handle: node.owner }
|
|
130
|
+
: node.assignee
|
|
131
|
+
? { handle: node.assignee }
|
|
132
|
+
: undefined,
|
|
127
133
|
detail: node.issueType,
|
|
128
134
|
meta: { issueType: node.issueType, prefix: node.prefix },
|
|
129
135
|
});
|
|
@@ -140,7 +146,13 @@ export function buildHistoricalFeed(
|
|
|
140
146
|
time,
|
|
141
147
|
nodeId: node.id,
|
|
142
148
|
nodeTitle: node.title,
|
|
143
|
-
actor: node.owner
|
|
149
|
+
actor: node.owner
|
|
150
|
+
? { handle: node.owner }
|
|
151
|
+
: node.createdBy
|
|
152
|
+
? { handle: node.createdBy }
|
|
153
|
+
: node.assignee
|
|
154
|
+
? { handle: node.assignee }
|
|
155
|
+
: undefined,
|
|
144
156
|
detail: node.closeReason || "Closed",
|
|
145
157
|
meta: { prefix: node.prefix },
|
|
146
158
|
});
|
|
@@ -155,11 +167,13 @@ export function buildHistoricalFeed(
|
|
|
155
167
|
if (!isNaN(time)) {
|
|
156
168
|
const src = typeof link.source === "object" ? (link.source as { id: string }).id : link.source;
|
|
157
169
|
const tgt = typeof link.target === "object" ? (link.target as { id: string }).id : link.target;
|
|
170
|
+
const sourceNode = nodes.find((n) => n.id === src);
|
|
158
171
|
add({
|
|
159
172
|
id: `link-added:${src}->${tgt}:${time}`,
|
|
160
173
|
type: "link-added",
|
|
161
174
|
time,
|
|
162
175
|
nodeId: src,
|
|
176
|
+
actor: sourceNode?.assignee ? { handle: sourceNode.assignee } : undefined,
|
|
163
177
|
detail: `${link.type} ${tgt}`,
|
|
164
178
|
meta: { linkType: link.type, target: tgt },
|
|
165
179
|
});
|
|
@@ -268,6 +282,7 @@ export function diffToActivityEvents(
|
|
|
268
282
|
time: now,
|
|
269
283
|
nodeId: id,
|
|
270
284
|
nodeTitle: node?.title,
|
|
285
|
+
actor: node?.assignee ? { handle: node.assignee } : undefined,
|
|
271
286
|
detail: node?.issueType || "task",
|
|
272
287
|
meta: node ? { issueType: node.issueType, prefix: node.prefix } : undefined,
|
|
273
288
|
});
|
|
@@ -275,11 +290,13 @@ export function diffToActivityEvents(
|
|
|
275
290
|
|
|
276
291
|
// Removed nodes
|
|
277
292
|
for (const id of diff.removedNodeIds) {
|
|
293
|
+
const node = nodeMap.get(id);
|
|
278
294
|
events.push({
|
|
279
295
|
id: `node-closed:${id}:${now}`,
|
|
280
296
|
type: "node-closed",
|
|
281
297
|
time: now,
|
|
282
298
|
nodeId: id,
|
|
299
|
+
actor: node?.assignee ? { handle: node.assignee } : undefined,
|
|
283
300
|
detail: "Removed",
|
|
284
301
|
});
|
|
285
302
|
}
|
|
@@ -314,6 +331,7 @@ export function diffToActivityEvents(
|
|
|
314
331
|
time: now,
|
|
315
332
|
nodeId: id,
|
|
316
333
|
nodeTitle: node?.title,
|
|
334
|
+
actor: node?.assignee ? { handle: node.assignee } : undefined,
|
|
317
335
|
detail: `${change.from} \u2192 ${change.to}`,
|
|
318
336
|
meta: { field: change.field, from: change.from, to: change.to },
|
|
319
337
|
});
|
|
@@ -326,11 +344,13 @@ export function diffToActivityEvents(
|
|
|
326
344
|
const match = key.match(/^(.+)->(.+):(.+)$/);
|
|
327
345
|
if (match) {
|
|
328
346
|
const [, src, tgt, linkType] = match;
|
|
347
|
+
const sourceNode = nodeMap.get(src);
|
|
329
348
|
events.push({
|
|
330
349
|
id: `link-added:${key}:${now}`,
|
|
331
350
|
type: "link-added",
|
|
332
351
|
time: now,
|
|
333
352
|
nodeId: src,
|
|
353
|
+
actor: sourceNode?.assignee ? { handle: sourceNode.assignee } : undefined,
|
|
334
354
|
detail: `${linkType} ${tgt}`,
|
|
335
355
|
meta: { linkType, target: tgt },
|
|
336
356
|
});
|
package/lib/parse-beads.ts
CHANGED
|
@@ -84,17 +84,31 @@ export function parseIssuesJsonl(beadsDir?: string): BeadIssue[] {
|
|
|
84
84
|
issues.push(...repoIssues);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
// Deduplicate by issue ID (primary repo wins
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
87
|
+
// Deduplicate by issue ID (primary repo wins for non-empty fields).
|
|
88
|
+
// When a duplicate is found, merge any empty fields from the duplicate into the first copy.
|
|
89
|
+
// This handles the case where the hub DB lost created_by/owner during import
|
|
90
|
+
// but individual repos have them.
|
|
91
|
+
const seen = new Map<string, BeadIssue>();
|
|
92
|
+
const result: BeadIssue[] = [];
|
|
93
|
+
for (const issue of issues) {
|
|
94
94
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
95
|
-
if ((issue as any).status === "tombstone")
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
if ((issue as any).status === "tombstone") continue;
|
|
96
|
+
const existing = seen.get(issue.id);
|
|
97
|
+
if (existing) {
|
|
98
|
+
// Merge: fill empty fields from duplicate
|
|
99
|
+
if (!existing.created_by && issue.created_by) existing.created_by = issue.created_by;
|
|
100
|
+
if (!existing.owner && issue.owner) existing.owner = issue.owner;
|
|
101
|
+
if (!existing.assignee && issue.assignee) existing.assignee = issue.assignee;
|
|
102
|
+
if (!existing.close_reason && issue.close_reason) existing.close_reason = issue.close_reason;
|
|
103
|
+
if (!existing.acceptance_criteria && issue.acceptance_criteria) existing.acceptance_criteria = issue.acceptance_criteria;
|
|
104
|
+
if (existing.estimated_minutes == null && issue.estimated_minutes != null) existing.estimated_minutes = issue.estimated_minutes;
|
|
105
|
+
if ((!existing.labels || existing.labels.length === 0) && issue.labels && issue.labels.length > 0) existing.labels = issue.labels;
|
|
106
|
+
} else {
|
|
107
|
+
seen.set(issue.id, issue);
|
|
108
|
+
result.push(issue);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return result;
|
|
98
112
|
}
|
|
99
113
|
|
|
100
114
|
/**
|
package/lib/types.ts
CHANGED
|
@@ -161,8 +161,9 @@ export const PRIORITY_COLORS: Record<number, string> = {
|
|
|
161
161
|
// Catppuccin Latte accent palette
|
|
162
162
|
// ============================================================================
|
|
163
163
|
|
|
164
|
-
/** Catppuccin Latte accent colors —
|
|
164
|
+
/** Catppuccin Latte accent colors — 85 colors for collision-free prefix mapping */
|
|
165
165
|
export const CATPPUCCIN_ACCENTS: string[] = [
|
|
166
|
+
// Original 14 Catppuccin Latte accents
|
|
166
167
|
"#d20f39", // Red
|
|
167
168
|
"#179299", // Teal
|
|
168
169
|
"#fe640b", // Peach
|
|
@@ -177,11 +178,91 @@ export const CATPPUCCIN_ACCENTS: string[] = [
|
|
|
177
178
|
"#7287fd", // Lavender
|
|
178
179
|
"#dd7878", // Flamingo
|
|
179
180
|
"#dc8a78", // Rosewater
|
|
181
|
+
// Extended palette (71 additional colors)
|
|
182
|
+
"#862d2d",
|
|
183
|
+
"#bf4040",
|
|
184
|
+
"#d27979",
|
|
185
|
+
"#931f1f",
|
|
186
|
+
"#d22d2d",
|
|
187
|
+
"#e06c6c",
|
|
188
|
+
"#a11212",
|
|
189
|
+
"#e61919",
|
|
190
|
+
"#ed5e5e",
|
|
191
|
+
"#86592d",
|
|
192
|
+
"#bf8040",
|
|
193
|
+
"#d2a679",
|
|
194
|
+
"#93591f",
|
|
195
|
+
"#d2802d",
|
|
196
|
+
"#e0a66c",
|
|
197
|
+
"#a15912",
|
|
198
|
+
"#e68019",
|
|
199
|
+
"#eda65e",
|
|
200
|
+
"#86862d",
|
|
201
|
+
"#bfbf40",
|
|
202
|
+
"#d2d279",
|
|
203
|
+
"#93931f",
|
|
204
|
+
"#d2d22d",
|
|
205
|
+
"#e0e06c",
|
|
206
|
+
"#a1a112",
|
|
207
|
+
"#e6e619",
|
|
208
|
+
"#eded5e",
|
|
209
|
+
"#59862d",
|
|
210
|
+
"#80bf40",
|
|
211
|
+
"#a6d279",
|
|
212
|
+
"#59931f",
|
|
213
|
+
"#80d22d",
|
|
214
|
+
"#a6e06c",
|
|
215
|
+
"#59a112",
|
|
216
|
+
"#80e619",
|
|
217
|
+
"#a6ed5e",
|
|
218
|
+
"#2d862d",
|
|
219
|
+
"#40bf40",
|
|
220
|
+
"#79d279",
|
|
221
|
+
"#1f931f",
|
|
222
|
+
"#2dd22d",
|
|
223
|
+
"#6ce06c",
|
|
224
|
+
"#12a112",
|
|
225
|
+
"#19e619",
|
|
226
|
+
"#5eed5e",
|
|
227
|
+
"#2d8659",
|
|
228
|
+
"#40bf80",
|
|
229
|
+
"#79d2a6",
|
|
230
|
+
"#1f9359",
|
|
231
|
+
"#2dd280",
|
|
232
|
+
"#6ce0a6",
|
|
233
|
+
"#12a159",
|
|
234
|
+
"#19e680",
|
|
235
|
+
"#5eeda6",
|
|
236
|
+
"#2d8686",
|
|
237
|
+
"#40bfbf",
|
|
238
|
+
"#79d2d2",
|
|
239
|
+
"#1f9393",
|
|
240
|
+
"#2dd2d2",
|
|
241
|
+
"#6ce0e0",
|
|
242
|
+
"#12a1a1",
|
|
243
|
+
"#19e6e6",
|
|
244
|
+
"#5eeded",
|
|
245
|
+
"#2d5986",
|
|
246
|
+
"#4080bf",
|
|
247
|
+
"#79a6d2",
|
|
248
|
+
"#1f5993",
|
|
249
|
+
"#2d80d2",
|
|
250
|
+
"#6ca6e0",
|
|
251
|
+
"#1259a1",
|
|
252
|
+
"#1980e6",
|
|
180
253
|
];
|
|
181
254
|
|
|
182
255
|
export const CATPPUCCIN_ACCENT_NAMES: string[] = [
|
|
183
256
|
"Red", "Teal", "Peach", "Blue", "Green", "Mauve", "Yellow",
|
|
184
257
|
"Sapphire", "Pink", "Sky", "Maroon", "Lavender", "Flamingo", "Rosewater",
|
|
258
|
+
"Color1", "Color2", "Color3", "Color4", "Color5", "Color6", "Color7", "Color8", "Color9", "Color10",
|
|
259
|
+
"Color11", "Color12", "Color13", "Color14", "Color15", "Color16", "Color17", "Color18", "Color19", "Color20",
|
|
260
|
+
"Color21", "Color22", "Color23", "Color24", "Color25", "Color26", "Color27", "Color28", "Color29", "Color30",
|
|
261
|
+
"Color31", "Color32", "Color33", "Color34", "Color35", "Color36", "Color37", "Color38", "Color39", "Color40",
|
|
262
|
+
"Color41", "Color42", "Color43", "Color44", "Color45", "Color46", "Color47", "Color48", "Color49", "Color50",
|
|
263
|
+
"Color51", "Color52", "Color53", "Color54", "Color55", "Color56", "Color57", "Color58", "Color59", "Color60",
|
|
264
|
+
"Color61", "Color62", "Color63", "Color64", "Color65", "Color66", "Color67", "Color68", "Color69", "Color70",
|
|
265
|
+
"Color71",
|
|
185
266
|
];
|
|
186
267
|
|
|
187
268
|
/** Catppuccin Latte Surface2 — used for "unassigned" / unknown values */
|
package/package.json
CHANGED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
[
|
|
2
|
-
{
|
|
3
|
-
"regexp": "^(?:\\/(_next\\/data\\/[^/]{1,}))?(?:\\/((?!login|api\\/auth|_next\\/static|_next\\/image|favicon\\.ico|image\\.png|opengraph-image\\.png|twitter-image\\.png).*))(\\\\.json)?[\\/#\\?]?$",
|
|
4
|
-
"originalSource": "/((?!login|api/auth|_next/static|_next/image|favicon\\.ico|image\\.png|opengraph-image\\.png|twitter-image\\.png).*)"
|
|
5
|
-
}
|
|
6
|
-
]
|