beads-map
v0.3.6
Published
Interactive dependency graph viewer for beads (bd) issues
Downloads
1,418
Maintainers
Readme
beads-map
Interactive dependency graph viewer for beads (bd) issues.

See your entire project's issues, epics, and dependencies as a live, explorable graph. beads-map auto-discovers your .beads/ directory and renders everything instantly -- no configuration needed.
Features
Graph Visualization
- 5 graph layouts -- Switch between layout modes via the toolbar:
- Force -- Organic force simulation with collision avoidance and variable link distances
- DAG -- Clean top-down directed acyclic graph with topological layering
- Radial -- Concentric rings by dependency depth. Root nodes (no blockers) sit at center, deeper dependencies on outer rings. Ring spacing scales with node count.
- Cluster -- Groups nodes spatially by project prefix. Each prefix gets its own cluster center arranged in a circle. Cross-project dependencies stretch visibly between clusters.
- Spread -- Like Force but maximally spaced for readability and screenshots. Stronger repulsion, wider link distances.
- Collapse / Expand -- Collapse all epics at once with a single button, or right-click individual epics to collapse/uncollapse them. Shows collapsed task count on each epic node.
- Visual encoding -- Node size = dependency importance (connection count), fill color = configurable via legend (see below), ring color = project prefix (Catppuccin Latte palette). Larger nodes are more connected; epics get a size boost.
- Legend color modes -- Switch node fill color between 5 modes via the bottom-right legend panel:
- Status (default) -- Open (green), In Progress (amber), Blocked (red), Deferred (zinc), Closed (emerald)
- Priority -- P0 Critical (red), P1 High (orange), P2 Medium (blue), P3 Low (zinc), P4 Backlog (zinc-300)
- Owner -- Color by
createdByfield using Catppuccin Latte accent palette (14 colors) - Assignee -- Color by
assigneefield using Catppuccin Latte accent palette - Prefix -- Color by project prefix using Catppuccin Latte accent palette
- Catppuccin Latte palette -- All prefix-colored elements (node rings, cluster circles, tooltip accent bars, prefix fill mode) use the Catppuccin Latte accent palette (14 saturated colors optimized for light backgrounds). Person/prefix mapping is deterministic via FNV-1a hash.
- Dependency arrows -- Solid emerald arrows with flow particles for blocking relationships, dashed zinc lines for parent-child hierarchy. Curved links with configurable curvature.
- Semantic zoom -- When zooming out, individual nodes smoothly fade and are replaced by epic cluster labels at each cluster's centroid. Clusters show the epic title, ID, and member count, surrounded by a dashed circle in the project's prefix color. Cluster visibility can be toggled with the "Clusters" button in the top-left toolbar.
- Spawn & exit animations -- New nodes pop in with an overshoot easing (easeOutBack), removed nodes shrink out, and status changes trigger a ripple animation. New links flash bright emerald on arrival.
- Hover tooltips -- Hover over any node to see a tooltip card with the project prefix, issue ID, title, creation date, blocker list, priority, owner, and assignee. Smart viewport clamping (prefers above cursor, flips below).
- Resizable minimap -- Always-visible minimap (bottom-left) showing all nodes, links, claimed avatars, and the current viewport rectangle. Click to navigate. Drag the top, right, or top-right corner handles to resize (100-500px wide, 80-400px tall).
Live Updates
- SSE streaming -- File watchers detect changes to
issues.jsonl(and all additional repo JSONL files) and push updates via Server-Sent Events. No refresh needed. - Diff/merge pipeline -- Incoming data is diffed against current state (
diffBeadsData) and merged with position preservation (mergeBeadsData) so the graph layout doesn't reset. Animation metadata (_spawnTime,_removeTime,_changedAt) is stamped during merge.
Timeline Replay
- Step-based playback -- Replay the entire history of your project as a step-by-step animation. Each step corresponds to one temporal event (issue creation, dependency addition).
- Play/pause controls -- Play, pause, and scrub through the timeline with a slider.
- Speed toggle -- 1x, 2x, 4x playback speed (2 seconds per event at 1x).
- Uses the same diff/merge pipeline as live updates, so nodes get proper positions and spawn animations during replay.
Search
- Smart search --
Cmd/Ctrl+Fto fuzzy search across all issues by ID, title, project prefix, owner, assignee, or commenter username. Keyboard navigation through results.
Right-Click Context Menu
- Multi-action menu -- Right-click any node to open a context menu with actions:
- Show description -- Opens a full-screen modal with the issue's markdown description
- Add comment -- Opens the comment tooltip for posting
- Claim task -- Posts a claim comment (
@handle) to mark yourself as working on the issue (only shown when authenticated and node is unclaimed) - Unclaim task -- Removes your claim from the node (only shown when you are the claimant)
- Collapse/Uncollapse epic -- Toggle individual epic collapse on right-click (only shown on epic nodes)
Task Claiming
- Claim tasks -- Right-click a node and select "Claim task" to mark yourself as working on it. Posts a special
@handlecomment via ATProto. - Avatar badges -- Claimed nodes display the claimant's circular avatar at the bottom-right of the node on the graph canvas. Avatars are drawn at constant screen-space size and also appear on the minimap.
- Avatar hover tooltip -- Hover over a claimed node's avatar to see the claimant's profile picture, handle, and when they claimed it (relative time).
- Optimistic UI -- Claims and unclaims update immediately in the UI before the server round-trip completes. Optimistic claims show the avatar instantly; optimistic unclaims suppress it instantly.
- Unclaim -- Remove your claim via the context menu. Deletes the underlying ATProto comment record.
Node Detail Sidebar
- Click-to-inspect -- Click any node to open a detail sidebar with:
- Issue ID, title, type icon, status/priority/prefix badges
- GitHub repository link (auto-detected from git remote, shown as clickable badge + URL)
- Metrics grid: blocks count, dependent count
- Blocker and dependent lists with clickable navigation
- Dates: created, updated, closed (with hour:minute precision)
- Owner attribution
- Full description rendered as Markdown (with GFM support)
- Copy-to-clipboard button for descriptions (copies raw markdown with a header showing project prefix, issue ID, and GitHub repo URL)
- Threaded comment section (see below)
- Description modal -- "View in window" opens a full-screen modal with the rendered description and a copy button. Also accessible via right-click context menu "Show description".
- Mobile drawer -- On small screens, the detail panel slides up as a bottom drawer instead of a sidebar.
ATProto Authentication & Comments
- OAuth 2.0 login -- Sign in with your Bluesky/ATProto account via OAuth 2.0 with PKCE. Avatar and handle shown in the header. Dual mode: public client for dev, confidential (ES256 JWK) for production.
- Comment annotations -- Right-click any node to post a comment via ATProto (
org.impactindexer.review.commentlexicon). Comments stored on the AT Protocol, fetched from the Hypergoat GraphQL indexer. - Threaded replies -- Comments support nested replies (
replyTofield). Root comments sorted newest-first, replies sorted chronologically. Rendered with indentation (ml-4 pl-3 border-l). - Likes -- Heart-toggle likes on comments (
org.impactindexer.review.likelexicon). Rose-colored when liked by the current user. - Delete -- Delete your own comments and likes.
- Comment badges -- Nodes with comments show a red notification badge with the comment count on the graph canvas.
- All Comments panel -- Slide-in sidebar showing all comments across all nodes with threaded replies, clickable node navigation pills, and like/delete actions.
Multi-Repo Support
- Automatic aggregation -- If your
.beads/config.yamllists additional repositories, beads-map loads all of them into a unified graph. - Per-project colors -- Each project prefix gets a deterministic color from the Catppuccin Latte palette (14 accent colors). Shown as node rings, prefix badges, cluster borders, and tooltip accent bars.
- GitHub repo links -- Auto-detects git remote URLs for each repository (primary + additional). Shown in the node detail card as a clickable GitHub link. Also included in copied description text.
Header / Navbar
- Frosted glass header --
bg-white/95 backdrop-blur-smsticky header inspired by plresearch.org. - Animated logo --
BeadsLogocomponent: an animated hexagonal network SVG with rotating concentric hexagons, pulsing vertex nodes, and a breathing center node -- representing the dependency graph. - Pill navigation -- Replay and Comments toggle buttons styled as rounded-full pill items. Auth button with rounded avatar dropdown.
- Centered search -- Rounded-full search bar with keyboard shortcut hint and dropdown results panel.
Info Panel & Legend
- Bottom-right info panel -- Shows issue count, dependency count, project count, color mode selector (Status / Priority / Owner / Assignee / Prefix), dynamic legend dots for the active mode, and visual encoding hints. Hidden during timeline replay. Legend shows only items present in visible nodes for owner/assignee/prefix modes.
Quick Start
cd ~/my-project
npx beads-map@latestbeads-map walks up from your current directory to find .beads/, just like git finds .git/.
With an explicit path
npx beads-map@latest --beads-dir ~/projects/my-project/.beadsMulti-repo aggregation
If your .beads/config.yaml lists additional repositories, beads-map automatically loads all of them:
# .beads/config.yaml
repos:
additional:
- ../backend
- ../frontend
- ../shared-libCLI Reference
beads-map [options]
Options:
--port <number> Port to serve on (default: 3000)
--beads-dir <path> Explicit .beads/ directory path
--dev Run in development mode (hot reload)
--help, -h Show this help message
Environment:
BEADS_DIR Override .beads/ discovery (same as --beads-dir)
Examples:
npx beads-map@latest # Auto-discover from cwd
npx beads-map@latest --port 4000 # Custom port
npx beads-map@latest --beads-dir ~/projects/hub/.beads # Explicit path
BEADS_DIR=../.beads npx beads-map@latest --dev # Dev mode with env varDevelopment
git clone https://github.com/GainForest/beads-map.git
cd beads-map
pnpm install
# Run in dev mode against a beads project
BEADS_DIR=~/path/to/.beads pnpm dev
# Build for production
pnpm buildQuality gate
pnpm build must pass with zero errors before committing. There are no tests yet -- the build is the sole gate.
Architecture
beads-map/
├── app/
│ ├── page.tsx # Main page: SSE wiring, merge logic, search, layout, comments, claims
│ ├── layout.tsx # Root layout, wraps in AuthProvider
│ ├── globals.css # Timeline slider styles, markdown prose, scrollbar
│ └── api/
│ ├── beads/
│ │ ├── route.ts # GET /api/beads -- one-shot full data load
│ │ └── stream/route.ts # GET /api/beads/stream -- SSE live updates
│ ├── config/route.ts # GET /api/config -- project name, repo count, repo URLs
│ ├── login/route.ts # POST /api/login -- initiate ATProto OAuth
│ ├── logout/route.ts # POST /api/logout -- clear session
│ ├── status/route.ts # GET /api/status -- current auth state
│ ├── records/route.ts # POST/PUT/DELETE /api/records -- ATProto record CRUD
│ └── oauth/
│ ├── callback/route.ts # OAuth callback handler
│ ├── client-metadata.json/ # OAuth client metadata
│ └── jwks.json/route.ts # JSON Web Key Set
├── components/
│ ├── AllCommentsPanel.tsx # Slide-in panel: all comments across nodes, threaded
│ ├── AuthButton.tsx # Sign-in modal + avatar dropdown (rounded-full pill style)
│ ├── BeadsGraph.tsx # Force graph: paintNode/paintLink, minimap, semantic zoom, avatars
│ ├── BeadsLogo.tsx # Animated hexagonal network SVG logo
│ ├── BeadTooltip.tsx # Hover tooltip: prefix, ID, title, date, blockers, priority, owner
│ ├── CommentTooltip.tsx # Floating right-click comment tooltip
│ ├── ContextMenu.tsx # Right-click context menu: description, comment, claim/unclaim
│ ├── DescriptionModal.tsx # Full-screen markdown description modal with copy button
│ ├── HeartIcon.tsx # Shared heart SVG (outline/filled)
│ ├── NodeDetail.tsx # Sidebar detail: metadata, deps, comments, repo link
│ ├── TimelineBar.tsx # Timeline replay: play/pause, scrubber, speed toggle
│ ├── GraphStats.tsx # Issue count statistics widget (unused, inlined in BeadsGraph)
│ └── StatusLegend.tsx # Color legend for statuses (unused, inlined in BeadsGraph)
├── hooks/
│ └── useBeadsComments.ts # Fetch comments + likes from Hypergoat, build thread trees
├── lib/
│ ├── auth.tsx # AuthProvider context + useAuth hook
│ ├── auth/client.ts # OAuth client factory (public/confidential mode)
│ ├── agent.ts # Authenticated ATProto agent from session
│ ├── session.ts # iron-session encrypted cookie setup
│ ├── env.ts # Environment variable validation
│ ├── discover.ts # .beads/ auto-discovery + git remote URL detection
│ ├── parse-beads.ts # JSONL parser, multi-repo hydration, graph builder
│ ├── types.ts # GraphNode, GraphLink, ColorMode, Catppuccin palette, color helpers
│ ├── diff-beads.ts # Diff engine for detecting added/removed/changed nodes/links
│ ├── watch-beads.ts # fs.watch wrapper with debounce for JSONL file changes
│ ├── timeline.ts # buildTimelineEvents + filterDataAtTime for replay
│ └── utils.ts # formatRelativeTime, buildDescriptionCopyText, shared utilities
├── bin/
│ └── beads-map.mjs # CLI entry point
├── scripts/
│ └── generate-jwk.js # ES256 JWK key generation for OAuth
└── public/
└── image.png # Screenshot for READMEData flow
- Discovery --
lib/discover.tswalks up fromcwd(or readsBEADS_DIR) to find.beads/. Also detects git remote URLs for all repos. - Parsing --
lib/parse-beads.tsreadsissues.jsonlfrom the primary repo and any additional repos inconfig.yaml, deduplicates, extracts dependencies, and builds a graph structure with computed fields (blocker/dependent counts, prefix colors). - Rendering --
components/BeadsGraph.tsxusesreact-force-graph-2dwith fully custom canvas rendering (paintNode,paintLink). All transient state (hover, selection, comments, claimed avatars) stored in refs to avoid re-rendering the force simulation. - Live updates --
app/api/beads/stream/route.tsopens an SSE connection, watches all JSONL files, and pushes full data on each change. The client diffs and merges with position preservation. - Timeline --
lib/timeline.tsextracts sorted temporal events from the data. During replay,filterDataAtTime()produces a time-slice that flows through the same diff/merge pipeline as live updates. - Comments --
hooks/useBeadsComments.tsqueries the Hypergoat GraphQL indexer for comments and likes, resolves profiles viapublic.api.bsky.app, and builds threaded trees client-side. - Claims -- A claim is a comment with text
@handle(starts with@, no spaces). TheclaimedNodeAvatarsuseMemo inpage.tsxscans all comments to build aMap<nodeId, claimInfo>. Avatars are drawn on canvas nodes and the minimap bypaintNodeviaclaimedNodeAvatarsRef.
Key design decisions
- paintNode/paintLink use refs, not props -- Callbacks have
[]dependencies and read fromselectedNodeRef,hoveredNodeRef,claimedNodeAvatarsRef, etc. This prevents re-creating the ForceGraph component on every interaction. - Position preservation is critical --
react-force-graph-2dmutates node objects in-place (x,y,vx,vy). The merge logic copies these from old nodes to new nodes to prevent layout resets. - Animation metadata convention -- Fields prefixed with
_on nodes/links (_spawnTime,_removeTime,_changedAt,_prevStatus) are transient, set bymergeBeadsData(), consumed bypaintNode/paintLink, and garbage-collected after 600ms. - Bootstrap trick -- The graph starts in DAG mode for 15ms to spread nodes into good positions, then auto-switches to Force mode. This gives the organic layout a clean starting arrangement.
- Link source/target normalization --
filterDataAtTime()must spread new link objects with stringsource/target(not the original mutated object refs from d3-force). Without this, links draw to wrong positions during timeline replay. - Optimistic claim/unclaim -- Two state variables:
optimisticClaims(Map) for immediate avatar display,optimisticUnclaims(Set) for immediate avatar suppression. Both reconciled with comment-derived data inclaimedNodeAvatarsuseMemo. - Avatar image cache -- Module-level
avatarImageCacheinBeadsGraph.tsx. NocrossOriginattribute on Image elements (Bluesky CDN CORS issue).getAvatarImage()returns cachedHTMLImageElementor starts loading and returns null. - Portal for modals --
DescriptionModalusescreatePortal(jsx, document.body)withz-[100]to escape any parent stacking contexts.
Tech Stack
- Next.js 14 (App Router)
- react-force-graph-2d for canvas-based graph rendering
- d3-force (forceCollide, forceRadial, forceX, forceY for layout modes)
- Catppuccin Latte accent palette for prefix/person coloring (14 colors)
- Tailwind CSS for styling
- TypeScript throughout
- @atproto/oauth-client-node + @atproto/api for ATProto authentication and record CRUD
- iron-session for encrypted cookie session management
- react-markdown + remark-gfm for description rendering
- Node.js
fs.watchfor file change detection - Server-Sent Events for real-time streaming
License
MIT
