Skip to content

feat(Beads): Implement full Kanban board UI for issue tracking#10

Merged
0xtsotsi merged 4 commits intomainfrom
codex/github-mention-featbeads-implement-full-kanban-board-ui
Dec 24, 2025
Merged

feat(Beads): Implement full Kanban board UI for issue tracking#10
0xtsotsi merged 4 commits intomainfrom
codex/github-mention-featbeads-implement-full-kanban-board-ui

Conversation

@0xtsotsi
Copy link
Owner

@0xtsotsi 0xtsotsi commented Dec 24, 2025

Summary

This PR implements a complete Kanban board UI for the Beads issue tracking integration, allowing users to manage issues visually through a full-featured board interface.

Changes

  • Implement full Kanban board UI for issue tracking
  • Fix Beads API payloads and harden CLI calls
  • Clean up deprecated client code and configuration files
  • Update documentation with AGENTS.md for team workflows
  • Enhance .env.example with comprehensive configuration

Test plan

  • Test Kanban board UI loads and displays issues correctly
  • Verify drag-and-drop functionality for issue state transitions
  • Test Beads API integration with various issue providers
  • Verify CLI call hardening prevents injection attacks
  • Confirm all linting and tests pass

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Full Beads issue tracker integrated: CRUD, ready-work, and validate actions exposed in app and UI
    • Kanban board with columns, drag-and-drop, search, filters, and dependency badges
    • Issue dialogs: create, edit, delete; header with stats and quick-add
    • Beads entry in sidebar and new CLI/UX scripts for running the Beads UI
  • Documentation

    • Added Beads UI integration guide in README
    • Added DevFlow agent guidelines (AGENTS.md)
  • Chores

    • Added example environment file and local version marker file

✏️ Tip: You can customize this high-level summary in your review settings.

0xtsotsi and others added 3 commits December 24, 2025 12:15
This PR implements a complete Kanban board interface for the Beads issue tracking system, providing a visual way to manage issues within DevFlow.

## Changes

### Frontend Components
- **beads-view.tsx**: Main entry point with complete UI implementation
- **BeadsHeader**: Header with stats, search, and add issue button
- **BeadsKanbanBoard**: Drag-and-drop Kanban board with columns (Backlog, Ready, In Progress, Closed)
- **BeadsCard**: Individual issue cards with metadata and actions
- **BeadsColumn**: Column components with issue count and drop zone
- **BeadsBadges**: Status and type badges with color coding

### Custom Hooks
- **useBeadsIssues**: Issue fetching and loading state
- **useBeadsColumnIssues**: Column filtering and statistics
- **useBeadsActions**: CRUD operations for issues
- **useBeadsDragDrop**: Drag-and-drop functionality

### Dialogs
- **CreateIssueDialog**: Form for creating new issues
- **EditIssueDialog**: Form for editing existing issues
- **DeleteIssueDialog**: Confirmation dialog with blocking count warning

### API Integration
- Added BeadsAPI types to electron.ts
- Implemented HTTP API client methods for all Beads operations
- Connected frontend to backend endpoints

### Documentation & Tooling
- Added Beads UI integration section to README.md
- Added npm scripts for beads-ui operations (beads, beads:open, beads:stop, etc.)
- Added .env.example file for configuration

## Features
- ✅ Visual Kanban board with 4 columns
- ✅ Drag-and-drop issue management
- ✅ Create, edit, and delete issues
- ✅ Search and filter issues
- ✅ Issue status tracking (Backlog → Ready → In Progress → Closed)
- ✅ Dependency tracking (blocking/blocked counts)
- ✅ Responsive design with theme support
- ✅ Loading and error states
- ✅ Toast notifications for user feedback

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Dec 24, 2025

Caution

Review failed

The pull request is closed.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Adds full Beads integration: backend service and routes, Electron/HTTP API surfaces, UI Kanban view (components, hooks, dialogs), types, state management, scripts, docs, and config files. No existing feature behavior was removed.

Changes

Cohort / File(s) Summary
Configuration & Metadata
\.beads/.local_version`, `.env.example`, `package.json``
Added local version marker, environment template for Beads UI/server, and npm scripts for beads management (open/stop/status/issues).
Documentation
AGENTS.md, README.md
New AGENTS.md with agent/workflow guidance; README extended with Beads UI Integration with install/usage notes.
Type Definitions
libs/types/src/beads.ts, libs/types/src/index.ts
New Beads types and re-exports (issues, dependencies, filters, validation, stats).
Server service
apps/server/src/services/beads-service.ts
New BeadsService wrapping the Beads CLI (install/validate/version, CRUD, ready/stats, DB sync + file watch).
Server routes & utils
apps/server/src/routes/beads/index.ts, apps/server/src/routes/beads/common.ts, apps/server/src/routes/beads/routes/*.ts
New /api/beads endpoints: list, create, update, delete, ready, validate with input validation, error logging, and delegation to BeadsService.
Server integration
apps/server/src/index.ts
Instantiated BeadsService and mounted /api/beads routes.
Electron & HTTP API
apps/ui/src/lib/electron.ts, apps/ui/src/lib/http-api-client.ts
Added BeadsAPI surface to ElectronAPI and HttpApiClient with list/create/update/delete/getReady/validate mappings.
UI state
apps/ui/src/store/app-store.ts
Added beads state (beadsByProject, readyWork, watching flags), actions, and added 'beads' view mode.
Navigation
apps/ui/src/components/layout/sidebar/hooks/use-navigation.ts
Added "Beads" navigation item and keyboard shortcut.
Beads View (entry)
apps/ui/src/components/views/beads-view.tsx
New BeadsView component coordinating hooks, dialogs, header, and Kanban board.
Kanban components & constants
apps/ui/src/components/views/beads-view/constants.ts, beads-header.tsx, beads-kanban-board.tsx, components/beads-*.tsx
New constants, header, Kanban board, column, card, and badge components (drag/drop, badges, labels, actions).
Dialogs
apps/ui/src/components/views/beads-view/dialogs/*.tsx
CreateIssueDialog, EditIssueDialog, DeleteIssueDialog with form state, validation, and callbacks.
UI hooks
apps/ui/src/components/views/beads-view/hooks/*.ts
useBeadsIssues, useBeadsColumnIssues, useBeadsActions, useBeadsDragDrop: loading, categorization, CRUD wrappers, drag/drop status transitions.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as BeadsView
    participant Hook as useBeadsActions
    participant Electron as ElectronAPI
    participant Client as HttpApiClient
    participant Server as /api/beads
    participant Service as BeadsService
    participant CLI as Beads CLI

    User->>UI: Open CreateIssueDialog & submit
    UI->>Hook: handleCreateIssue(input)
    Hook->>Electron: getElectronAPI().beads.create(projectPath, input)
    Electron->>Client: beads.create(...)
    Client->>Server: POST /api/beads/create
    Server->>Service: createIssue(projectPath, issue)
    Service->>CLI: bd issue create ...
    CLI-->>Service: created issue
    Service-->>Server: BeadsIssue
    Server-->>Client: { success: true, issue }
    Client-->>Hook: { success: true, issue }
    Hook->>UI: addBeadsIssue -> update store
    UI-->>User: Issue created (toast)
Loading
sequenceDiagram
    actor User
    participant UI as BeadsKanbanBoard
    participant DnD as useBeadsDragDrop
    participant Hook as useBeadsActions
    participant Client as /api/beads

    User->>UI: Drag card -> drop on column
    UI->>DnD: handleDragStart(issueId)
    UI->>UI: Render drag overlay
    User->>UI: Drop card
    UI->>DnD: handleDragEnd(overId/column)
    DnD->>Hook: handleStatusChange(issueId, newStatus)
    Hook->>Client: update(projectPath, issueId, { status: newStatus })
    Client-->>Hook: { success: true, issue }
    Hook->>UI: updateBeadsIssue -> store update
    UI-->>User: Card moved (toast)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I hopped in code to plant a seed,

Beads now bloom from task to deed.
Kanban columns, badges bright,
Drag and drop from morn to night.
Hop, tap, sync — the board takes flight!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.35% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: implementing a Kanban board UI for Beads issue tracking, which aligns with the substantial changeset focused on frontend UI components, hooks, dialogs, and integration with the Beads service.

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 898d325 and fd31d73.

📒 Files selected for processing (7)
  • README.md
  • apps/server/src/index.ts
  • apps/ui/src/components/layout/sidebar/hooks/use-navigation.ts
  • apps/ui/src/lib/electron.ts
  • apps/ui/src/lib/http-api-client.ts
  • apps/ui/src/store/app-store.ts
  • package.json

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link

Summary of Changes

Hello @0xtsotsi, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the issue tracking capabilities by integrating a complete Kanban board UI for the Beads system. This allows users to visually manage tasks, bugs, and features, improving workflow and collaboration. The changes span both frontend and backend, providing robust API support for Beads operations and detailed documentation for AI agent interaction.

Highlights

  • Kanban Board UI: Introduced a full-featured Kanban board UI for the Beads issue tracking system, allowing visual management of tasks and issues.
  • Beads API Integration: Implemented new backend API endpoints and a dedicated service for interacting with the Beads CLI, enabling programmatic issue creation, updates, deletion, and listing.
  • Agent Workflow Documentation: Added comprehensive guidelines in AGENTS.md to instruct AI agents on effective utilization of the Beads system for task management, including command references and workflow patterns.
  • Enhanced Configuration and Client Code: Updated the .env.example with new Beads UI configuration options and cleaned up deprecated client-side code, improving maintainability and setup.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This is a substantial pull request that introduces a full-featured Kanban board UI for Beads issue tracking. The changes are extensive, covering backend services, API routes, UI components, state management, and documentation. The implementation of the backend service and the UI with custom hooks is well-structured.

However, I've found a few critical issues that need to be addressed:

  • There is a critical command injection vulnerability in apps/server/src/services/beads-service.ts due to the use of exec with unsanitized input.
  • The execAsync function is used but not defined, which will cause a runtime error.
  • A runtime file with a hardcoded user path (.beads/daemon.lock) has been added to the repository.

I've provided detailed comments and suggestions for these and other issues. Great work on this large feature!

Comment on lines +8 to +13
import { execFile } from 'child_process';
import { promisify } from 'util';
import path from 'path';
import fs from 'fs/promises';

const execFileAsync = promisify(execFile);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The execAsync function is used later in this file but is not defined or imported. This will cause a runtime error. You should import exec from child_process and create a promisified version of it.

Suggested change
import { execFile } from 'child_process';
import { promisify } from 'util';
import path from 'path';
import fs from 'fs/promises';
const execFileAsync = promisify(execFile);
import { exec, execFile } from 'child_process';
import { promisify } from 'util';
import path from 'path';
import fs from 'fs/promises';
const execFileAsync = promisify(execFile);
const execAsync = promisify(exec);

Comment on lines +262 to +273
async addDependency(
projectPath: string,
issueId: string,
depId: string,
type: 'blocks' | 'related' | 'parent' | 'discovered-from'
): Promise<void> {
try {
const command = `bd dep add ${issueId} ${depId} --type ${type}`;
await execAsync(command, { cwd: projectPath });
} catch (error) {
throw new Error(`Failed to add dependency: ${error}`);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This method uses string concatenation to build a shell command, which is a command injection vulnerability. A malicious issueId or depId could execute arbitrary code. Please use execFileAsync with an array of arguments to prevent this. This issue also applies to removeDependency, getReadyWork, getStats, and sync.

  async addDependency(
    projectPath: string,
    issueId: string,
    depId: string,
    type: 'blocks' | 'related' | 'parent' | 'discovered-from'
  ): Promise<void> {
    try {
      await execFileAsync('bd', ['dep', 'add', issueId, depId, '--type', type], { cwd: projectPath });
    } catch (error) {
      throw new Error(`Failed to add dependency: ${error}`);
    }
  }

Comment on lines +1 to +7
{
"pid": 8024,
"parent_pid": 7988,
"database": "/home/oxtsotsi/Webrnds/DevFlow/.beads/beads.db",
"version": "0.34.0",
"started_at": "2025-12-24T05:32:47.425187425Z"
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This file appears to be a runtime lock file and contains a hardcoded, user-specific path (/home/oxtsotsi/...). Such files should not be committed to the repository. Please add .beads/ to your .gitignore file to prevent these files from being tracked.

Comment on lines +27 to +31
const { columnIssuesMap, stats } = useBeadsColumnIssues({
issues,
searchQuery,
currentProject,
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The searchQuery state is used in the useBeadsColumnIssues hook before it is declared on line 49. This will cause a ReferenceError at runtime. The useState declaration for searchQuery should be moved to before the useBeadsColumnIssues hook is called.

descContains?: string;
ids?: string[];
}
): Promise<any[]> {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This function returns Promise<any[]>. To improve type safety, it should return Promise<BeadsIssue[]>, using the BeadsIssue type defined in @automaker/types. This applies to other methods in this service as well, which should also use specific types instead of any. You'll need to import these types.

Suggested change
): Promise<any[]> {
): Promise<BeadsIssue[]> {

Comment on lines +81 to +84
} else {
// Other cases go to backlog
map.backlog.push(issue);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for categorizing issues into columns seems to leave the backlog column empty in most cases. Open issues are either categorized as ready (if they have no blockers) or blocked. The final else block that would push to backlog is unlikely to be reached. Is this the intended behavior? If so, the backlog column might be redundant. If not, the logic may need adjustment to correctly populate the backlog.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🧹 Nitpick comments (14)
.env.example (1)

8-10: Update the example path to use a generic placeholder.

The example path contains a hardcoded username /home/oxtsotsi/, which should be replaced with a generic placeholder like /home/username/ or $HOME/ to avoid confusion.

🔎 Proposed fix
-# Path to the bd binary (optional, beads-ui usually auto-detects)
-# Only needed if bd is not in your standard PATH
-# BD_BIN=/home/oxtsotsi/.local/bin/bd
+# Path to the bd binary (optional, beads-ui usually auto-detects)
+# Only needed if bd is not in your standard PATH
+# BD_BIN=$HOME/.local/bin/bd
apps/server/src/routes/beads/routes/ready.ts (1)

9-29: Consider validating the limit parameter.

The limit parameter from the request body is passed directly to beadsService.getReadyWork() without validation. While this may be handled by the underlying service or CLI, adding validation here would provide clearer error messages and prevent potential issues with negative or invalid values.

🔎 Proposed validation
     const { projectPath, limit } = req.body as {
       projectPath: string;
       limit?: number;
     };

     if (!projectPath) {
       res.status(400).json({ success: false, error: 'projectPath is required' });
       return;
     }
+
+    if (limit !== undefined && (typeof limit !== 'number' || limit < 1 || !Number.isInteger(limit))) {
+      res.status(400).json({ success: false, error: 'limit must be a positive integer' });
+      return;
+    }

     const issues = await beadsService.getReadyWork(projectPath, limit);
apps/ui/src/components/views/beads-view/constants.ts (1)

12-43: Remove or update the unused beadsStatus field in column definitions.

The beadsStatus field is not referenced anywhere in the codebase and does not drive column differentiation. The actual filtering logic in use-beads-column-issues.ts ignores this field entirely and instead differentiates columns using issue.status combined with blocker metadata (hasOpenBlockers()):

  • Blocked: open issues with blocking dependencies
  • Ready: open issues without blockers
  • Backlog: fallback for non-standard statuses

The beadsStatus field is dead code and misleading—it suggests filtering by status alone when the actual logic requires checking blocker state. Either remove it or update the type/values to accurately reflect the implementation.

apps/ui/src/components/views/beads-view/components/beads-card.tsx (2)

117-121: Add accessible label to action menu button.

The dropdown menu trigger button only contains an icon without an accessible label. This makes it difficult for screen reader users to understand the button's purpose.

🔎 Suggested fix
-              <Button variant="ghost" size="sm" className="h-6 w-6 p-0">
+              <Button variant="ghost" size="sm" className="h-6 w-6 p-0" aria-label="Issue actions">
                 <MoreHorizontal className="h-3 w-3" />
               </Button>

112-112: Consider simplifying status label logic.

The current ternary for status display works but could be more maintainable using a status map. This would also make it easier to add proper capitalization for all statuses.

🔎 Suggested refactor

At the top of the component:

const STATUS_LABELS: Record<BeadsIssueStatus, string> = {
  open: 'Open',
  in_progress: 'In Progress',
  closed: 'Closed',
};

Then use it:

-            {issue.status === 'in_progress' ? 'In Progress' : issue.status.replace('_', ' ')}
+            {STATUS_LABELS[issue.status]}
apps/ui/src/components/views/beads-view/dialogs/edit-issue-dialog.tsx (1)

79-98: Add error handling for robustness.

If onUpdate throws an exception instead of returning false, the isUpdating state will remain true, leaving the dialog in a stuck loading state. Consider wrapping the call in a try-catch block to ensure the loading state is always cleared.

🔎 Suggested fix
   const handleUpdate = async () => {
     if (!issue || !title.trim()) {
       return;
     }
 
     setIsUpdating(true);
-    const success = await onUpdate(issue.id, {
-      title: title.trim(),
-      description: description.trim(),
-      type,
-      priority,
-      labels,
-    });
-
-    setIsUpdating(false);
-
-    if (success) {
-      onOpenChange(false);
+    try {
+      const success = await onUpdate(issue.id, {
+        title: title.trim(),
+        description: description.trim(),
+        type,
+        priority,
+        labels,
+      });
+      
+      if (success) {
+        onOpenChange(false);
+      }
+    } finally {
+      setIsUpdating(false);
     }
   };
apps/ui/src/components/views/beads-view/beads-header.tsx (2)

1-1: Remove unused imports.

The imports useState and useCallback are not used in this component and can be removed.

🔎 Suggested fix
-import { useState, useCallback, memo } from 'react';
+import { memo } from 'react';

79-94: Consider improving placeholder filter UX.

The type filter Select is marked as a placeholder but appears fully interactive, which may confuse users when their selections have no effect. Consider either disabling it with a tooltip, or connecting it to actual filtering logic if possible.

Alternative approaches

Option 1: Disable until implemented

<Select defaultValue="all" disabled>
  <SelectTrigger className="w-[140px] h-9" title="Coming soon">
    <Filter className="h-4 w-4 mr-2" />
    <SelectValue placeholder="Type" />
  </SelectTrigger>
  {/* ... */}
</Select>

Option 2: Remove until ready to implement (cleanest approach)

apps/ui/src/components/views/beads-view/dialogs/create-issue-dialog.tsx (1)

78-93: Add error handling for robustness.

Similar to the EditIssueDialog, if onCreate throws an exception, the isCreating state will remain true, leaving the dialog stuck in a loading state. Wrap the call in try-finally to ensure cleanup.

🔎 Suggested fix
   const handleCreate = async () => {
     if (!title.trim()) {
       return;
     }
 
     setIsCreating(true);
-    const success = await onCreate({
-      title: title.trim(),
-      description: description.trim(),
-      type,
-      priority,
-      labels: labels.length > 0 ? labels : undefined,
-    });
-
-    setIsCreating(false);
-
-    if (success) {
-      onOpenChange(false);
+    try {
+      const success = await onCreate({
+        title: title.trim(),
+        description: description.trim(),
+        type,
+        priority,
+        labels: labels.length > 0 ? labels : undefined,
+      });
+      
+      if (success) {
+        onOpenChange(false);
+      }
+    } finally {
+      setIsCreating(false);
     }
   };
apps/ui/src/components/views/beads-view.tsx (1)

52-71: Consider extracting getBlockingCounts to a shared utility.

The same logic appears in beads-kanban-board.tsx (lines 45-61), creating code duplication. Extracting this to a shared utility function would improve maintainability and ensure consistency.

💡 Suggested refactor

Create a new utility file apps/ui/src/components/views/beads-view/utils/blocking-counts.ts:

import type { BeadsIssue } from '@automaker/types';

export function getBlockingCounts(issue: BeadsIssue, issues: BeadsIssue[]) {
  const blockingCount = issues.filter((otherIssue) =>
    otherIssue.dependencies?.some((dep) => dep.issueId === issue.id && dep.type === 'blocks')
  ).length;

  const blockedCount =
    issue.dependencies?.filter((dep) => {
      const depIssue = issues.find((i) => i.id === dep.issueId);
      return (
        dep.type === 'blocks' &&
        depIssue &&
        (depIssue.status === 'open' || depIssue.status === 'in_progress')
      );
    }).length || 0;

  return { blockingCount, blockedCount };
}

Then import and use it in both files.

apps/ui/src/components/views/beads-view/beads-kanban-board.tsx (1)

45-61: Code duplication: Extract getBlockingCounts to shared utility.

This function duplicates the same logic from beads-view.tsx (lines 52-71). Please refer to the refactoring suggestion in that file.

apps/ui/src/components/views/beads-view/hooks/use-beads-column-issues.ts (2)

5-15: Unused prop: currentProject is not referenced.

The currentProject prop is included in the interface but never used in the hook implementation. Consider removing it to keep the API surface clean.

🔎 Proposed fix
 interface UseBeadsColumnIssuesProps {
   issues: BeadsIssue[];
   searchQuery: string;
-  currentProject: { path: string } | null;
 }
 
 export function useBeadsColumnIssues({
   issues,
   searchQuery,
-  currentProject,
 }: UseBeadsColumnIssuesProps) {

110-118: Consider performance optimization for large issue lists.

The stats calculation calls hasOpenBlockers for each issue (line 115), which iterates through all issues, resulting in O(n²) complexity. For typical issue counts (hundreds), this is acceptable and properly memoized. However, for very large issue lists (10k+), consider pre-computing a blocker map for O(n) performance.

libs/types/src/beads.ts (1)

80-95: Consider reusing BeadsDependency type for consistency.

Line 92 defines dependencies as Array<{ issueId: string; type: BeadsDependencyType }>, which duplicates the structure of BeadsDependency. For consistency and maintainability, consider reusing the existing type.

🔎 Proposed fix
   /** Optional dependencies to add */
-  dependencies?: Array<{ issueId: string; type: BeadsDependencyType }>;
+  dependencies?: BeadsDependency[];
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9106264 and 898d325.

⛔ Files ignored due to path filters (2)
  • .beads/beads.db is excluded by !**/*.db
  • .beads/daemon.lock is excluded by !**/*.lock
📒 Files selected for processing (39)
  • .beads/.local_version
  • .env.example
  • AGENTS.md
  • README.md
  • apps/server/src/index.ts
  • apps/server/src/routes/beads/common.ts
  • apps/server/src/routes/beads/index.ts
  • apps/server/src/routes/beads/routes/create.ts
  • apps/server/src/routes/beads/routes/delete.ts
  • apps/server/src/routes/beads/routes/list.ts
  • apps/server/src/routes/beads/routes/ready.ts
  • apps/server/src/routes/beads/routes/update.ts
  • apps/server/src/routes/beads/routes/validate.ts
  • apps/server/src/services/beads-service.ts
  • apps/ui/src/components/layout/sidebar/hooks/use-navigation.ts
  • apps/ui/src/components/views/beads-view.tsx
  • apps/ui/src/components/views/beads-view/beads-header.tsx
  • apps/ui/src/components/views/beads-view/beads-kanban-board.tsx
  • apps/ui/src/components/views/beads-view/components/beads-badges.tsx
  • apps/ui/src/components/views/beads-view/components/beads-card.tsx
  • apps/ui/src/components/views/beads-view/components/beads-column.tsx
  • apps/ui/src/components/views/beads-view/constants.ts
  • apps/ui/src/components/views/beads-view/dialogs/create-issue-dialog.tsx
  • apps/ui/src/components/views/beads-view/dialogs/delete-issue-dialog.tsx
  • apps/ui/src/components/views/beads-view/dialogs/edit-issue-dialog.tsx
  • apps/ui/src/components/views/beads-view/hooks/use-beads-actions.ts
  • apps/ui/src/components/views/beads-view/hooks/use-beads-column-issues.ts
  • apps/ui/src/components/views/beads-view/hooks/use-beads-drag-drop.ts
  • apps/ui/src/components/views/beads-view/hooks/use-beads-issues.ts
  • apps/ui/src/lib/electron.ts
  • apps/ui/src/lib/http-api-client.ts
  • apps/ui/src/store/app-store.ts
  • libs/types/src/beads.ts
  • libs/types/src/index.ts
  • lint-server-output.txt
  • lint-ui-output.txt
  • package.json
  • typecheck-server-output.txt
  • typecheck-ui-output.txt
🧰 Additional context used
🧬 Code graph analysis (18)
apps/server/src/routes/beads/index.ts (8)
apps/server/src/services/beads-service.ts (1)
  • BeadsService (15-381)
apps/server/src/middleware/validate-paths.ts (1)
  • validatePathParams (22-69)
apps/server/src/routes/beads/routes/list.ts (1)
  • createListHandler (9-38)
apps/server/src/routes/beads/routes/create.ts (1)
  • createCreateHandler (9-40)
apps/server/src/routes/beads/routes/update.ts (1)
  • createUpdateHandler (9-47)
apps/server/src/routes/beads/routes/delete.ts (1)
  • createDeleteHandler (9-35)
apps/server/src/routes/beads/routes/ready.ts (1)
  • createReadyWorkHandler (9-29)
apps/server/src/routes/beads/routes/validate.ts (1)
  • createValidateHandler (9-29)
apps/ui/src/components/views/beads-view/constants.ts (1)
apps/ui/src/components/views/beads-view/components/beads-column.tsx (1)
  • BeadsColumn (20-89)
apps/server/src/routes/beads/routes/create.ts (1)
apps/server/src/routes/beads/common.ts (2)
  • logError (12-15)
  • getErrorMessage (5-10)
apps/ui/src/components/views/beads-view/components/beads-column.tsx (2)
apps/ui/src/components/views/beads-view/constants.ts (2)
  • BeadsColumnId (45-45)
  • BeadsColumn (5-10)
apps/ui/src/lib/utils.ts (1)
  • cn (5-7)
apps/ui/src/lib/electron.ts (1)
libs/types/src/index.ts (4)
  • ListBeadsIssuesFilters (95-95)
  • BeadsIssue (87-87)
  • CreateBeadsIssueInput (93-93)
  • UpdateBeadsIssueInput (94-94)
apps/ui/src/components/views/beads-view.tsx (6)
apps/ui/src/components/views/beads-view/hooks/use-beads-issues.ts (1)
  • useBeadsIssues (11-86)
apps/ui/src/components/views/beads-view/hooks/use-beads-column-issues.ts (1)
  • useBeadsColumnIssues (11-125)
apps/ui/src/components/views/beads-view/hooks/use-beads-actions.ts (1)
  • useBeadsActions (17-148)
libs/types/src/beads.ts (1)
  • BeadsIssue (36-65)
apps/ui/src/lib/electron.ts (1)
  • getElectronAPI (689-698)
apps/ui/src/components/views/beads-view/dialogs/delete-issue-dialog.tsx (1)
  • DeleteIssueDialog (22-76)
apps/server/src/routes/beads/routes/update.ts (1)
apps/server/src/routes/beads/common.ts (2)
  • logError (12-15)
  • getErrorMessage (5-10)
apps/ui/src/components/views/beads-view/hooks/use-beads-column-issues.ts (2)
apps/ui/src/components/views/beads-view/constants.ts (1)
  • BeadsColumnId (45-45)
.github/scripts/upload-to-r2.js (1)
  • stats (43-43)
apps/server/src/routes/beads/routes/validate.ts (2)
apps/server/src/services/beads-service.ts (1)
  • BeadsService (15-381)
apps/server/src/routes/beads/common.ts (2)
  • logError (12-15)
  • getErrorMessage (5-10)
apps/ui/src/components/views/beads-view/dialogs/create-issue-dialog.tsx (2)
apps/ui/src/components/views/beads-view/constants.ts (1)
  • DEFAULT_PRIORITY (74-74)
apps/ui/src/components/ui/button.tsx (1)
  • Button (108-108)
apps/server/src/index.ts (2)
apps/server/src/services/beads-service.ts (1)
  • BeadsService (15-381)
apps/server/src/routes/beads/index.ts (1)
  • createBeadsRoutes (15-26)
apps/ui/src/components/views/beads-view/hooks/use-beads-actions.ts (1)
apps/ui/src/lib/electron.ts (1)
  • getElectronAPI (689-698)
apps/ui/src/components/views/beads-view/hooks/use-beads-issues.ts (2)
apps/ui/src/lib/electron.ts (1)
  • getElectronAPI (689-698)
.github/scripts/upload-to-r2.js (1)
  • result (67-157)
apps/ui/src/components/views/beads-view/hooks/use-beads-drag-drop.ts (1)
apps/ui/src/components/views/beads-view/constants.ts (1)
  • BeadsColumnId (45-45)
apps/server/src/routes/beads/routes/delete.ts (1)
apps/server/src/routes/beads/common.ts (2)
  • logError (12-15)
  • getErrorMessage (5-10)
apps/ui/src/store/app-store.ts (1)
libs/types/src/beads.ts (3)
  • BeadsIssue (36-65)
  • BeadsIssueStatus (12-12)
  • BeadsIssueType (17-17)
apps/ui/src/components/views/beads-view/beads-header.tsx (1)
apps/ui/src/components/ui/button.tsx (1)
  • Button (108-108)
apps/ui/src/lib/http-api-client.ts (2)
apps/ui/src/lib/electron.ts (1)
  • BeadsAPI (104-152)
libs/types/src/index.ts (3)
  • ListBeadsIssuesFilters (95-95)
  • CreateBeadsIssueInput (93-93)
  • UpdateBeadsIssueInput (94-94)
🪛 Biome (2.1.2)
apps/ui/src/components/views/beads-view.tsx

[error] 29-29: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)

🪛 markdownlint-cli2 (0.18.1)
AGENTS.md

311-311: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (39)
.beads/.local_version (1)

1-1: LGTM! Version marker is clear.

The version string is well-formatted and serves as a clear reference for the Beads integration version.

.env.example (1)

26-61: LGTM! Server configuration is well-documented.

The Automaker Server configuration is comprehensive and includes helpful comments for each setting. Port separation between Beads UI (3000) and Automaker Server (3008) is appropriate.

README.md (2)

40-40: LGTM! Table of Contents updated correctly.

The Beads UI Integration entry is properly added to the Table of Contents with correct markdown linking.


238-383: LGTM! Comprehensive Beads UI documentation.

The Beads UI Integration section is thorough and well-structured, covering installation, usage, npm scripts, keyboard shortcuts, troubleshooting, and integration notes. The documentation properly addresses the potential port conflict between Beads UI (default 3000) and other services.

package.json (1)

47-52: LGTM! Beads npm scripts are well-defined.

The new npm scripts provide convenient shortcuts for common Beads UI operations and align with the documentation in README.md. The trailing comma on the prepare script (line 47) correctly accommodates the new script entries.

AGENTS.md (1)

1-410: Excellent agent guidelines documentation.

This documentation provides comprehensive guidance for AI agents working with the Beads task management system. The workflow patterns, command references, best practices, and examples are well-structured and actionable.

apps/ui/src/components/layout/sidebar/hooks/use-navigation.ts (1)

6-6: LGTM! Beads navigation integration is clean.

The Beads navigation item is properly integrated into the Project section with an appropriate icon (ListTodo) and keyboard shortcut support. The implementation follows the existing navigation pattern consistently.

Also applies to: 30-30, 135-140

apps/server/src/index.ts (1)

51-52: LGTM! Beads service integration follows established patterns.

The BeadsService is properly instantiated and mounted at /api/beads, following the same pattern as other services in the application. Unlike AgentService, BeadsService doesn't require async initialization as it performs lazy validation checks at runtime.

Also applies to: 121-121, 155-155

apps/server/src/routes/beads/common.ts (1)

5-15: LGTM!

Simple, reusable error handling utilities with proper type safety for unknown error types.

apps/ui/src/components/views/beads-view/constants.ts (1)

47-72: LGTM!

Priority and type color mappings are well-structured with proper dark mode support.

apps/ui/src/lib/electron.ts (2)

4-11: LGTM!

BeadsAPI type definition follows the existing API pattern with consistent return types and proper TypeScript typing.

Also applies to: 103-152


454-454: LGTM!

Optional beads API properly integrated into the ElectronAPI interface.

apps/server/src/routes/beads/routes/create.ts (1)

9-40: LGTM!

Input validation is comprehensive, error handling is consistent, and the code properly uses the BeadsService. Path validation is handled by the middleware in the router configuration.

apps/server/src/routes/beads/index.ts (1)

15-26: LGTM!

Route wiring is clean with proper path validation middleware applied consistently. The /validate endpoint correctly omits path validation since it can operate without a specific project path.

apps/server/src/routes/beads/routes/update.ts (1)

9-47: LGTM!

Consistent input validation and error handling pattern matching the other route handlers.

apps/server/src/routes/beads/routes/list.ts (1)

9-38: LGTM!

Clean implementation with optional filter support and consistent error handling.

libs/types/src/index.ts (1)

85-98: LGTM! Clean type re-exports.

The new Beads type exports follow the established pattern in the file and correctly use type-only exports. The addition expands the public API appropriately for the Beads integration.

apps/server/src/routes/beads/routes/delete.ts (2)

1-3: Verify HTTP method choice for delete operation.

The endpoint is documented as "POST /delete" but DELETE operations typically use the HTTP DELETE method. If the request requires a body with additional parameters like force, POST is acceptable, but consider whether a DELETE method with query parameters or a custom header might be more RESTful.


9-35: Well-structured delete handler.

The implementation follows good practices with clear validation, proper error handling, and consistent use of error utilities. The logic correctly validates required fields, invokes the service, and handles both success and error cases.

apps/ui/src/components/views/beads-view/components/beads-card.tsx (1)

34-170: Well-implemented draggable card component.

The component correctly implements drag-and-drop with appropriate disabled states, handles event propagation properly, and provides context-sensitive actions based on issue status. The conditional rendering and callback patterns are sound.

apps/ui/src/components/views/beads-view/dialogs/edit-issue-dialog.tsx (1)

100-219: Well-structured form dialog with good UX.

The form provides clear labeling, proper validation, loading states, and keyboard shortcuts (Enter to add labels). The implementation correctly handles form state and user interactions.

apps/server/src/routes/beads/routes/validate.ts (1)

9-29: LGTM! Clear validation endpoint logic.

The handler correctly implements both global installation checks and project-specific validation. The conditional logic is clear, error handling is consistent with other routes, and the service calls match the expected signatures.

Note: The project validation path can return { success: true, error: "..." } when validation detects issues (e.g., CLI not installed). This is acceptable if "success" refers to the HTTP request succeeding rather than validation passing.

apps/ui/src/components/views/beads-view/beads-header.tsx (1)

23-105: Clean header component with good visual hierarchy.

The component effectively presents project stats and provides clear controls for search and issue creation. The layout is well-organized with appropriate visual styling for different stat types.

apps/ui/src/components/views/beads-view/dialogs/create-issue-dialog.tsx (1)

41-215: Well-implemented create dialog with proper form reset.

The dialog correctly resets state when opened, provides good UX with keyboard shortcuts and loading states, and properly handles optional fields (sending undefined for empty labels rather than an empty array).

apps/ui/src/components/views/beads-view/components/beads-column.tsx (1)

20-89: Good visual design and drop indicator.

The column structure with background layers, header styling, and drop-zone indicators provides clear visual feedback for drag-and-drop operations. The conditional styling and opacity handling are well implemented.

apps/ui/src/components/views/beads-view/hooks/use-beads-drag-drop.ts (1)

14-103: LGTM!

The drag-and-drop logic is well-structured:

  • Correctly maps columns to statuses
  • Handles both direct column drops and drops onto issues
  • Properly determines blocked status by checking if blocking dependencies are still open/in_progress
  • Includes appropriate guards for edge cases (no drop target, unchanged status)
  • Returns a clean API with activeIssue state and handlers
apps/ui/src/store/app-store.ts (1)

535-552: LGTM!

The Beads state integration is well-designed:

  • Per-project state isolation with beadsByProject keyed by project path
  • Comprehensive action set for CRUD operations, view management, and filtering
  • Proper immutable state updates following Zustand patterns
  • Consistent with existing store architecture

The implementation correctly:

  • Initializes default state for new projects
  • Updates timestamps on mutations
  • Guards against missing project entries
  • Maintains backward compatibility with existing state

Also applies to: 868-879, 981-985, 2596-2739

apps/ui/src/components/views/beads-view/hooks/use-beads-actions.ts (1)

17-148: LGTM!

The actions hook is well-implemented with:

  • Consistent error handling across all operations (project validation, API availability checks, exception handling)
  • Proper user feedback via toast notifications with descriptive messages
  • Correct store updates using the app store's Beads actions
  • Type-safe return values and function signatures
  • Appropriate use of useCallback for memoization

All four handlers (create, update, delete, status change) follow the same reliable pattern.

apps/ui/src/components/views/beads-view/hooks/use-beads-issues.ts (1)

11-86: LGTM!

The issues loading hook demonstrates thoughtful UX considerations:

  • Only displays loading spinner on initial load, avoiding flicker on subsequent fetches
  • Validates Beads initialization before attempting to load issues
  • Gracefully handles uninitialized Beads by setting empty issues array
  • Preserves existing issues on error instead of clearing them (better user experience)
  • Properly tracks project switches to manage loading state
  • Clean API with issues, loading state, error message, and reload function
apps/ui/src/components/views/beads-view/dialogs/delete-issue-dialog.tsx (1)

22-76: LGTM!

The delete dialog provides excellent UX:

  • Clear destructive styling with AlertTriangle icon signals the dangerous action
  • Displays issue details (title and ID) for confirmation
  • Shows prominent warning when the issue blocks other issues, informing the user of potential impact
  • Proper button states: disabled during deletion with loading text feedback
  • Clean prop interface aligning with the async delete flow
apps/ui/src/lib/http-api-client.ts (1)

27-27: LGTM!

The Beads API integration is correctly implemented:

  • All six methods (list, create, update, delete, getReady, validate) properly mapped to /api/beads/* endpoints
  • Correct payload structures with projectPath and method-specific parameters
  • Type-safe with proper imports from @automaker/types
  • Follows existing patterns in the HTTP client
  • Consistent with the BeadsAPI interface contract

Also applies to: 32-36, 762-775

apps/ui/src/components/views/beads-view/components/beads-badges.tsx (5)

10-24: LGTM!

The TypeBadge component correctly handles type rendering with a sensible fallback and proper styling.


31-41: LGTM!

The PriorityIndicator component provides good accessibility with both visual (colored dot) and textual (label) representations of priority, plus a descriptive tooltip.


48-63: LGTM!

The BlockingBadge component correctly handles the zero-count case and provides clear, pluralized messaging with appropriate styling.


70-85: LGTM!

The BlockedBadge component mirrors the structure of BlockingBadge with appropriate red styling to convey the blocking state clearly.


92-107: LGTM!

The LabelsList component correctly handles empty/null cases and renders labels with appropriate styling. Using label as the key is acceptable assuming labels are unique within an issue (which is the typical case).

libs/types/src/beads.ts (3)

36-65: Verify that labels and dependencies are always defined.

The labels and dependencies fields are typed as required arrays (lines 50, 52), but usage in use-beads-column-issues.ts includes defensive checks:

  • Line 18: if (!issue.dependencies) return false;
  • Line 63: issue.labels?.some(...)

If these fields can be undefined at runtime, update the types to reflect that:

🔎 Suggested type adjustment if fields can be undefined
   /** Labels for categorization */
-  labels: string[];
+  labels?: string[];
   /** Dependencies on other issues */
-  dependencies: BeadsDependency[];
+  dependencies?: BeadsDependency[];

If the backend guarantees these are always present (possibly as empty arrays), document this invariant and remove the defensive checks in consuming code.


118-134: LGTM!

The ListBeadsIssuesFilters interface is comprehensive and well-designed, offering flexible filtering options with clear documentation of semantics (e.g., labels use AND logic).


139-166: LGTM!

Both BeadsValidationResult and BeadsStats interfaces are well-designed with clear documentation and appropriate field types for their intended purposes.

Comment on lines +311 to +354
```
User: Fix the login form validation bug

Agent: I'll help fix the login form validation. Let me start by checking existing issues.

$ bd ready
# Output: No ready work

$ bd list --title-contains "login"
# Output: No existing login issues

$ bd create "Fix login form validation" --type bug --priority 1 --description "Login form is not validating email format correctly"
# Output: Created bd-a1b2

$ bd update bd-a1b2 --status in_progress

[Agent investigates the code...]

Agent: I found the validation bug. However, I also discovered that the password field has the same issue.

$ bd create "Fix password field validation" --type bug --priority 1 --description "Password field not validating requirements"
# Output: Created bd-c3d4

$ bd dep add bd-c3d4 discovered-from bd-a1b2
$ bd dep add bd-a1b2 related bd-c3d4

[Agent fixes login validation...]

$ bd update bd-a1b2 --status closed
$ bd update bd-c3d4 --status in_progress

[Agent fixes password validation...]

$ bd update bd-c3d4 --status closed

$ bd ready
# Output: No ready work

$ bd sync
$ git add .beads/beads.jsonl
$ git commit -m "Fix login and password validation bugs"

Agent: I've completed both fixes. The login form now validates email format correctly, and the password field enforces the required complexity rules. Both issues are closed and synced.
```
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add language identifier to the fenced code block.

The fenced code block starting at line 311 is missing a language identifier, which can affect syntax highlighting and proper rendering in markdown viewers.

🔎 Proposed fix
-```
+```text
 User: Fix the login form validation bug

Or alternatively, if this is meant to represent a shell session:

-```
+```bash
 User: Fix the login form validation bug
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

311-311: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
In AGENTS.md around lines 311 to 354, the fenced code block beginning at line
311 is missing a language identifier; update the opening triple-backtick to
include an appropriate language (e.g., ```text if it's plain text or ```bash if
it's a shell session) so markdown renderers will apply correct syntax
highlighting, keeping the block content unchanged.

Comment on lines +8 to +13
import { execFile } from 'child_process';
import { promisify } from 'util';
import path from 'path';
import fs from 'fs/promises';

const execFileAsync = promisify(execFile);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Missing import for execAsync.

Lines 262-338 use execAsync but it's not imported. Only execFile is imported from child_process. This will cause runtime errors.

🔎 Proposed fix
-import { execFile } from 'child_process';
+import { execFile, exec } from 'child_process';
 import { promisify } from 'util';
 import path from 'path';
 import fs from 'fs/promises';
 
 const execFileAsync = promisify(execFile);
+const execAsync = promisify(exec);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { execFile } from 'child_process';
import { promisify } from 'util';
import path from 'path';
import fs from 'fs/promises';
const execFileAsync = promisify(execFile);
import { execFile, exec } from 'child_process';
import { promisify } from 'util';
import path from 'path';
import fs from 'fs/promises';
const execFileAsync = promisify(execFile);
const execAsync = promisify(exec);
🤖 Prompt for AI Agents
In apps/server/src/services/beads-service.ts around lines 8 to 13, the file only
imports execFile and creates execFileAsync but later code uses execAsync which
is not defined; add an import for exec from 'child_process' and create a
promisified execAsync (using util.promisify) alongside execFileAsync so
execAsync is available for the code that expects it.

Comment on lines +262 to +274
async addDependency(
projectPath: string,
issueId: string,
depId: string,
type: 'blocks' | 'related' | 'parent' | 'discovered-from'
): Promise<void> {
try {
const command = `bd dep add ${issueId} ${depId} --type ${type}`;
await execAsync(command, { cwd: projectPath });
} catch (error) {
throw new Error(`Failed to add dependency: ${error}`);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Command injection vulnerability in addDependency.

String concatenation is used to build shell commands, which is vulnerable to command injection if issueId, depId, or type contain malicious input (e.g., ; rm -rf /).

🔎 Proposed fix using execFileAsync
   async addDependency(
     projectPath: string,
     issueId: string,
     depId: string,
     type: 'blocks' | 'related' | 'parent' | 'discovered-from'
   ): Promise<void> {
     try {
-      const command = `bd dep add ${issueId} ${depId} --type ${type}`;
-      await execAsync(command, { cwd: projectPath });
+      await execFileAsync('bd', ['dep', 'add', issueId, depId, '--type', type], { 
+        cwd: projectPath 
+      });
     } catch (error) {
       throw new Error(`Failed to add dependency: ${error}`);
     }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async addDependency(
projectPath: string,
issueId: string,
depId: string,
type: 'blocks' | 'related' | 'parent' | 'discovered-from'
): Promise<void> {
try {
const command = `bd dep add ${issueId} ${depId} --type ${type}`;
await execAsync(command, { cwd: projectPath });
} catch (error) {
throw new Error(`Failed to add dependency: ${error}`);
}
}
async addDependency(
projectPath: string,
issueId: string,
depId: string,
type: 'blocks' | 'related' | 'parent' | 'discovered-from'
): Promise<void> {
try {
await execFileAsync('bd', ['dep', 'add', issueId, depId, '--type', type], {
cwd: projectPath
});
} catch (error) {
throw new Error(`Failed to add dependency: ${error}`);
}
}
🤖 Prompt for AI Agents
In apps/server/src/services/beads-service.ts around lines 262 to 274, the
addDependency function builds a shell command via string concatenation which
permits command injection through issueId, depId, or type; change it to call a
safe child process API that accepts an argv array (e.g.,
util.promisify(child_process.execFile) or spawn with args) instead of exec with
a single shell string, pass the command as the executable ("bd") and each
parameter as a separate argument (["dep","add", issueId, depId, "--type", type])
while ensuring type is validated against the allowed set before calling, run
with { cwd: projectPath } and rethrow or wrap the caught error with its message
as before.

Comment on lines +279 to +286
async removeDependency(projectPath: string, issueId: string, depId: string): Promise<void> {
try {
const command = `bd dep remove ${issueId} ${depId}`;
await execAsync(command, { cwd: projectPath });
} catch (error) {
throw new Error(`Failed to remove dependency: ${error}`);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Command injection vulnerability in removeDependency.

String concatenation creates a command injection risk if issueId or depId contain shell metacharacters.

🔎 Proposed fix using execFileAsync
   async removeDependency(projectPath: string, issueId: string, depId: string): Promise<void> {
     try {
-      const command = `bd dep remove ${issueId} ${depId}`;
-      await execAsync(command, { cwd: projectPath });
+      await execFileAsync('bd', ['dep', 'remove', issueId, depId], { 
+        cwd: projectPath 
+      });
     } catch (error) {
       throw new Error(`Failed to remove dependency: ${error}`);
     }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async removeDependency(projectPath: string, issueId: string, depId: string): Promise<void> {
try {
const command = `bd dep remove ${issueId} ${depId}`;
await execAsync(command, { cwd: projectPath });
} catch (error) {
throw new Error(`Failed to remove dependency: ${error}`);
}
}
async removeDependency(projectPath: string, issueId: string, depId: string): Promise<void> {
try {
await execFileAsync('bd', ['dep', 'remove', issueId, depId], {
cwd: projectPath
});
} catch (error) {
throw new Error(`Failed to remove dependency: ${error}`);
}
}
🤖 Prompt for AI Agents
In apps/server/src/services/beads-service.ts around lines 279 to 286 the current
implementation builds a shell command via string concatenation which allows
command injection through issueId or depId; replace execAsync(command, { cwd })
with a safe child_process execution that accepts the executable and argument
array (e.g., execFileAsync or spawn with args) so the bd binary is invoked with
['dep','remove', issueId, depId] and not via a shell, validate or whitelist
issueId/depId formats (e.g., alphanumeric, dashes, underscores) before calling,
and propagate or wrap errors as before so the catch still throws a helpful Error
with the underlying message.

Comment on lines +291 to +306
async getReadyWork(projectPath: string, limit?: number): Promise<any[]> {
try {
let command = 'bd ready --json';
if (limit) {
command += ` --limit ${limit}`;
}
const { stdout } = await execAsync(command, { cwd: projectPath });
const issues = JSON.parse(stdout);
return issues;
} catch (error) {
if (this.isNotInitializedError(error)) {
return [];
}
throw error;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Command injection vulnerability in getReadyWork.

String concatenation to build the command allows injection if limit is manipulated or if the command string is tampered with.

🔎 Proposed fix using execFileAsync
   async getReadyWork(projectPath: string, limit?: number): Promise<any[]> {
     try {
-      let command = 'bd ready --json';
-      if (limit) {
-        command += ` --limit ${limit}`;
-      }
-      const { stdout } = await execAsync(command, { cwd: projectPath });
+      const args = ['ready', '--json'];
+      if (limit) {
+        args.push('--limit', String(limit));
+      }
+      const { stdout } = await execFileAsync('bd', args, { cwd: projectPath });
       const issues = JSON.parse(stdout);
       return issues;
     } catch (error) {
       if (this.isNotInitializedError(error)) {
         return [];
       }
       throw error;
     }
   }
🤖 Prompt for AI Agents
In apps/server/src/services/beads-service.ts around lines 291 to 306, the
command is built via string concatenation which permits command injection when
limit is user-controlled; replace the exec-style invocation with a safe
child-process call that accepts an args array (e.g., execFileAsync or
spawn/execFile wrapper) and pass 'bd' as the executable and ['ready', '--json',
'--limit', String(limit)] only when limit is present; additionally
validate/ensure limit is a positive integer before including it (or omit the
--limit arg if invalid) and keep the cwd option when invoking the safe
execFile/spawn wrapper so JSON parsing remains unchanged.

Comment on lines +343 to +368
async watchDatabase(projectPath: string, callback: () => void): Promise<() => void> {
const dbPath = this.getDatabasePath(projectPath);

try {
const watcher = fs.watch(dbPath, () => {
// Debounce rapid changes
if (this.watchTimeout) {
clearTimeout(this.watchTimeout);
}
this.watchTimeout = setTimeout(() => {
callback();
}, 500);
});

// Return cleanup function
return () => {
watcher.close();
if (this.watchTimeout) {
clearTimeout(this.watchTimeout);
}
};
} catch (error) {
// If watching fails (e.g., database doesn't exist), return no-op cleanup
return () => {};
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, check the imports at the top of the file
head -20 apps/server/src/services/beads-service.ts

Repository: 0xtsotsi/DevFlow

Length of output: 552


🏁 Script executed:

#!/bin/bash
# Also verify what fs APIs are actually being used throughout the file
rg -n "import.*from\s+['\"]fs" apps/server/src/services/beads-service.ts

Repository: 0xtsotsi/DevFlow

Length of output: 92


🏁 Script executed:

#!/bin/bash
# Check the actual watchDatabase method and surrounding code
sed -n '343,368p' apps/server/src/services/beads-service.ts

Repository: 0xtsotsi/DevFlow

Length of output: 785


🏁 Script executed:

#!/bin/bash
# Check if there are other uses of fs in this file to assess pattern consistency
rg -n "fs\." apps/server/src/services/beads-service.ts | head -20

Repository: 0xtsotsi/DevFlow

Length of output: 180


🏁 Script executed:

#!/bin/bash
# Check if there are any other fs imports in the file
rg -n "^import.*fs" apps/server/src/services/beads-service.ts

Repository: 0xtsotsi/DevFlow

Length of output: 92


Fix: fs.watch() is not available on the fs/promises import and will cause a runtime error.

Line 11 imports fs from fs/promises, which provides promise-based file operations. However, line 347 calls fs.watch(), which only exists on the callback-based fs module. The fs/promises namespace does not include the watch() method, so this code will crash at runtime with "fs.watch is not a function" when watchDatabase() is called.

Import the callback-based fs module separately to use fs.watch():

import fs from 'fs';
import { promises as fsPromises } from 'fs';

Then replace fs.access() calls on lines 60 and 74 with fsPromises.access(), and keep the fs.watch() call using the callback-based module.

🤖 Prompt for AI Agents
In apps/server/src/services/beads-service.ts around lines 343 to 368, the code
calls fs.watch() but the file currently imports fs from 'fs/promises', which
lacks watch and will throw at runtime; change imports to use the callback-based
fs for watch and the promises API for async access (e.g., import fs from 'fs'
and import { promises as fsPromises } from 'fs'), update any fs.access() calls
referenced earlier (lines ~60 and ~74) to use fsPromises.access(), and keep
fs.watch() using the callback-based fs so watchDatabase() uses the correct API
and the access calls remain promise-based.

Comment on lines +27 to +31
const { columnIssuesMap, stats } = useBeadsColumnIssues({
issues,
searchQuery,
currentProject,
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix variable declaration order.

searchQuery is used on Line 29 before its declaration on Line 49, violating JavaScript's temporal dead zone rules and causing a ReferenceError.

🔎 Proposed fix

Move the searchQuery state declaration before the hook calls that depend on it:

 export function BeadsView() {
   const { currentProject } = useAppStore();
 
+  // Search state
+  const [searchQuery, setSearchQuery] = useState('');
+
   // Custom hooks
   const { issues, isLoading, error, loadIssues } = useBeadsIssues({ currentProject });
   const { columnIssuesMap, stats } = useBeadsColumnIssues({
     issues,
     searchQuery,
     currentProject,
   });
   const { handleCreateIssue, handleUpdateIssue, handleDeleteIssue, handleStatusChange } =
     useBeadsActions({
       currentProject,
       loadIssues,
     });
   const { activeIssue, handleDragStart, handleDragEnd } = useBeadsDragDrop({
     issues,
     handleStatusChange,
   });
 
   // Dialog states
   const [showCreateDialog, setShowCreateDialog] = useState(false);
   const [showEditDialog, setShowEditDialog] = useState(false);
   const [showDeleteDialog, setShowDeleteDialog] = useState(false);
   const [selectedIssue, setSelectedIssue] = useState<BeadsIssue | null>(null);
-
-  // Search state
-  const [searchQuery, setSearchQuery] = useState('');

Also applies to: 49-49

🧰 Tools
🪛 Biome (2.1.2)

[error] 29-29: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)

🤖 Prompt for AI Agents
In apps/ui/src/components/views/beads-view.tsx around lines 27-31 (and line 49),
the hook useBeadsColumnIssues is using searchQuery before searchQuery is
declared which triggers a TDZ ReferenceError; fix by moving the searchQuery
state declaration (and its setter) above any hook calls that depend on it (i.e.,
declare const [searchQuery, setSearchQuery] = useState(...) before calling
useBeadsColumnIssues) so all hooks receive a defined variable, and update any
related imports/usages if necessary.

Comment on lines +155 to +161
const handleCreateFromDialog = useCallback(
async (input: any) => {
const result = await handleCreateIssue(input);
return result !== null;
},
[handleCreateIssue]
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Replace any type with proper type annotation.

The input parameter should use CreateBeadsIssueInput type, which is already imported from @automaker/types.

🔎 Proposed fix
   const handleCreateFromDialog = useCallback(
-    async (input: any) => {
+    async (input: CreateBeadsIssueInput) => {
       const result = await handleCreateIssue(input);
       return result !== null;
     },
     [handleCreateIssue]
   );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleCreateFromDialog = useCallback(
async (input: any) => {
const result = await handleCreateIssue(input);
return result !== null;
},
[handleCreateIssue]
);
const handleCreateFromDialog = useCallback(
async (input: CreateBeadsIssueInput) => {
const result = await handleCreateIssue(input);
return result !== null;
},
[handleCreateIssue]
);
🤖 Prompt for AI Agents
In apps/ui/src/components/views/beads-view.tsx around lines 155 to 161, the
handleCreateFromDialog callback uses a parameter typed as any; replace that with
the proper CreateBeadsIssueInput type (imported from @automaker/types) so the
signature becomes async (input: CreateBeadsIssueInput) => { ... }; update any
related usages if needed to satisfy TypeScript and keep the dependency array
[handleCreateIssue] unchanged.

'scroll-smooth'
)}
>
<SortableContext items={children} strategy={verticalListSortingStrategy}>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: SortableContext items prop has incorrect type.

The items prop of SortableContext expects an array of sortable IDs ((string | number)[]), but this code passes children which is of type ReactNode. This will cause type errors and prevent drag-and-drop sorting from working correctly within columns.

🔎 Proposed fix

Add an items prop to the component interface and pass it to SortableContext:

 interface BeadsColumnProps {
   id: BeadsColumnId;
   title: string;
   colorClass: string;
   count: number;
   children: ReactNode;
+  items: string[];
   headerAction?: ReactNode;
   opacity?: number;
   showBorder?: boolean;
   hideScrollbar?: boolean;
 }
 
 export const BeadsColumn = memo(function BeadsColumn({
   id,
   title,
   colorClass,
   count,
   children,
+  items,
   headerAction,
   opacity = 100,
   showBorder = true,
   hideScrollbar = false,
 }: BeadsColumnProps) {
   const { setNodeRef, isOver } = useDroppable({ id });
 
   return (
     // ...
     <div className="...">
-      <SortableContext items={children} strategy={verticalListSortingStrategy}>
+      <SortableContext items={items} strategy={verticalListSortingStrategy}>
         {children}
       </SortableContext>
     </div>
   );
 });

The parent component (BeadsKanbanBoard) should pass the array of issue IDs separately from the rendered children.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/ui/src/components/views/beads-view/components/beads-column.tsx around
line 78, the SortableContext is passed children (ReactNode) but expects an array
of sortable IDs (string|number)[], causing type errors; update the BeadsColumn
component props to accept an items: (string|number)[] prop (provided by the
parent BeadsKanbanBoard), and pass that items array into <SortableContext
items={items} ...>, ensuring the parent supplies the correct ID array separate
from the rendered children so drag-and-drop works and types align.

Comment on lines +17 to +32
const hasOpenBlockers = (issue: BeadsIssue, allIssues: BeadsIssue[]): boolean => {
if (!issue.dependencies) return false;

// Check each dependency
for (const dep of issue.dependencies) {
// Only check 'blocks' type dependencies
if (dep.type === 'blocks') {
const depIssue = allIssues.find((i) => i.id === dep.issueId);
// If the blocking issue is open or in progress, it's blocking this issue
if (depIssue && (depIssue.status === 'open' || depIssue.status === 'in_progress')) {
return true;
}
}
}
return false;
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical logic error: Dependency direction is inverted.

The function hasOpenBlockers checks if an issue has dependencies with type 'blocks', but the semantics are inverted. According to the type documentation (line 26 in beads.ts), a 'blocks' dependency means "must complete before" — if issue A has a dependency {type: 'blocks', issueId: 'B'}, it means A blocks B (A must complete before B).

However, this function is trying to determine if the current issue is blocked by others. It should be searching for issues where other issues have a 'blocks' dependency pointing to this issue.

Current logic: "Does this issue block others?" (incorrect for hasOpenBlockers)
Intended logic: "Is this issue blocked by others?"

🔎 Proposed fix
-  // Helper to check if an issue has open blockers
-  const hasOpenBlockers = (issue: BeadsIssue, allIssues: BeadsIssue[]): boolean => {
-    if (!issue.dependencies) return false;
-
-    // Check each dependency
-    for (const dep of issue.dependencies) {
-      // Only check 'blocks' type dependencies
-      if (dep.type === 'blocks') {
-        const depIssue = allIssues.find((i) => i.id === dep.issueId);
-        // If the blocking issue is open or in progress, it's blocking this issue
-        if (depIssue && (depIssue.status === 'open' || depIssue.status === 'in_progress')) {
-          return true;
-        }
-      }
-    }
-    return false;
-  };
+  // Helper to check if an issue has open blockers
+  const hasOpenBlockers = (issue: BeadsIssue, allIssues: BeadsIssue[]): boolean => {
+    // Find issues that block this issue (other issues with 'blocks' dependency pointing to this issue)
+    return allIssues.some((otherIssue) => {
+      if (!otherIssue.dependencies) return false;
+      if (otherIssue.status === 'closed') return false; // Closed issues don't block
+      
+      return otherIssue.dependencies.some(
+        (dep) => dep.issueId === issue.id && dep.type === 'blocks'
+      );
+    });
+  };
🤖 Prompt for AI Agents
In apps/ui/src/components/views/beads-view/hooks/use-beads-column-issues.ts
around lines 17 to 32, the function currently inspects the current issue's
dependencies for type 'blocks' (which means "this issue blocks another"), but we
need to detect whether this issue is blocked by others. Replace the logic so you
iterate over allIssues (excluding or including the current issue), and for each
candidate issue check its dependencies for an entry with type === 'blocks' and
issueId === issue.id; if such a dependency exists and that candidate issue's
status is 'open' or 'in_progress', return true; otherwise return false after
checking all issues. Ensure you handle missing dependencies arrays safely.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +27 to +31
const { columnIssuesMap, stats } = useBeadsColumnIssues({
issues,
searchQuery,
currentProject,
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Initialize searchQuery before using it

The hook call here reads searchQuery before it’s initialized (const [searchQuery, …] = useState('') appears later). Because searchQuery is a const, this triggers a temporal-dead-zone ReferenceError on the first render, so the Beads view will crash as soon as it loads. Move the useState declaration above this hook call (or inline a default) to avoid the runtime error.

Useful? React with 👍 / 👎.

…featbeads-implement-full-kanban-board-ui

# Conflicts:
#	apps/server/src/index.ts
#	apps/server/src/routes/beads/common.ts
#	apps/server/src/routes/beads/index.ts
#	apps/server/src/routes/beads/routes/create.ts
#	apps/server/src/routes/beads/routes/list.ts
#	apps/server/src/routes/beads/routes/ready.ts
#	apps/server/src/routes/beads/routes/update.ts
#	apps/ui/src/lib/http-api-client.ts
#	apps/ui/src/store/app-store.ts
#	libs/types/src/beads.ts
#	libs/types/src/index.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant