Conversation
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>
|
Caution Review failedThe pull request is closed. Note Other AI code review bot(s) detectedCodeRabbit 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. 📝 WalkthroughWalkthroughAdds 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
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)
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)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
📜 Recent review detailsConfiguration used: defaults Review profile: CHILL Plan: Pro 📒 Files selected for processing (7)
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. Comment |
Summary of ChangesHello @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
Using Gemini Code AssistThe 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
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 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
|
There was a problem hiding this comment.
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.tsdue to the use ofexecwith unsanitized input. - The
execAsyncfunction 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!
| import { execFile } from 'child_process'; | ||
| import { promisify } from 'util'; | ||
| import path from 'path'; | ||
| import fs from 'fs/promises'; | ||
|
|
||
| const execFileAsync = promisify(execFile); |
There was a problem hiding this comment.
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.
| 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); |
| 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}`); | ||
| } |
There was a problem hiding this comment.
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}`);
}
}| { | ||
| "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" | ||
| } |
| const { columnIssuesMap, stats } = useBeadsColumnIssues({ | ||
| issues, | ||
| searchQuery, | ||
| currentProject, | ||
| }); |
| descContains?: string; | ||
| ids?: string[]; | ||
| } | ||
| ): Promise<any[]> { |
There was a problem hiding this comment.
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.
| ): Promise<any[]> { | |
| ): Promise<BeadsIssue[]> { |
| } else { | ||
| // Other cases go to backlog | ||
| map.backlog.push(issue); | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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/bdapps/server/src/routes/beads/routes/ready.ts (1)
9-29: Consider validating the limit parameter.The
limitparameter from the request body is passed directly tobeadsService.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 unusedbeadsStatusfield in column definitions.The
beadsStatusfield is not referenced anywhere in the codebase and does not drive column differentiation. The actual filtering logic inuse-beads-column-issues.tsignores this field entirely and instead differentiates columns usingissue.statuscombined with blocker metadata (hasOpenBlockers()):
- Blocked: open issues with blocking dependencies
- Ready: open issues without blockers
- Backlog: fallback for non-standard statuses
The
beadsStatusfield 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
onUpdatethrows an exception instead of returningfalse, theisUpdatingstate will remaintrue, 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
useStateanduseCallbackare 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
onCreatethrows an exception, theisCreatingstate will remaintrue, 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 extractinggetBlockingCountsto 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: ExtractgetBlockingCountsto 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:currentProjectis not referenced.The
currentProjectprop 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
hasOpenBlockersfor 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 reusingBeadsDependencytype for consistency.Line 92 defines dependencies as
Array<{ issueId: string; type: BeadsDependencyType }>, which duplicates the structure ofBeadsDependency. 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
⛔ Files ignored due to path filters (2)
.beads/beads.dbis excluded by!**/*.db.beads/daemon.lockis excluded by!**/*.lock
📒 Files selected for processing (39)
.beads/.local_version.env.exampleAGENTS.mdREADME.mdapps/server/src/index.tsapps/server/src/routes/beads/common.tsapps/server/src/routes/beads/index.tsapps/server/src/routes/beads/routes/create.tsapps/server/src/routes/beads/routes/delete.tsapps/server/src/routes/beads/routes/list.tsapps/server/src/routes/beads/routes/ready.tsapps/server/src/routes/beads/routes/update.tsapps/server/src/routes/beads/routes/validate.tsapps/server/src/services/beads-service.tsapps/ui/src/components/layout/sidebar/hooks/use-navigation.tsapps/ui/src/components/views/beads-view.tsxapps/ui/src/components/views/beads-view/beads-header.tsxapps/ui/src/components/views/beads-view/beads-kanban-board.tsxapps/ui/src/components/views/beads-view/components/beads-badges.tsxapps/ui/src/components/views/beads-view/components/beads-card.tsxapps/ui/src/components/views/beads-view/components/beads-column.tsxapps/ui/src/components/views/beads-view/constants.tsapps/ui/src/components/views/beads-view/dialogs/create-issue-dialog.tsxapps/ui/src/components/views/beads-view/dialogs/delete-issue-dialog.tsxapps/ui/src/components/views/beads-view/dialogs/edit-issue-dialog.tsxapps/ui/src/components/views/beads-view/hooks/use-beads-actions.tsapps/ui/src/components/views/beads-view/hooks/use-beads-column-issues.tsapps/ui/src/components/views/beads-view/hooks/use-beads-drag-drop.tsapps/ui/src/components/views/beads-view/hooks/use-beads-issues.tsapps/ui/src/lib/electron.tsapps/ui/src/lib/http-api-client.tsapps/ui/src/store/app-store.tslibs/types/src/beads.tslibs/types/src/index.tslint-server-output.txtlint-ui-output.txtpackage.jsontypecheck-server-output.txttypecheck-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
/validateendpoint 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
beadsByProjectkeyed 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
useCallbackfor memoizationAll 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
projectPathand method-specific parameters- Type-safe with proper imports from
@automaker/types- Follows existing patterns in the HTTP client
- Consistent with the
BeadsAPIinterface contractAlso applies to: 32-36, 762-775
apps/ui/src/components/views/beads-view/components/beads-badges.tsx (5)
10-24: LGTM!The
TypeBadgecomponent correctly handles type rendering with a sensible fallback and proper styling.
31-41: LGTM!The
PriorityIndicatorcomponent provides good accessibility with both visual (colored dot) and textual (label) representations of priority, plus a descriptive tooltip.
48-63: LGTM!The
BlockingBadgecomponent correctly handles the zero-count case and provides clear, pluralized messaging with appropriate styling.
70-85: LGTM!The
BlockedBadgecomponent mirrors the structure ofBlockingBadgewith appropriate red styling to convey the blocking state clearly.
92-107: LGTM!The
LabelsListcomponent correctly handles empty/null cases and renders labels with appropriate styling. Usinglabelas 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 thatlabelsanddependenciesare always defined.The
labelsanddependenciesfields are typed as required arrays (lines 50, 52), but usage inuse-beads-column-issues.tsincludes defensive checks:
- Line 18:
if (!issue.dependencies) return false;- Line 63:
issue.labels?.some(...)If these fields can be
undefinedat 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
ListBeadsIssuesFiltersinterface is comprehensive and well-designed, offering flexible filtering options with clear documentation of semantics (e.g., labels use AND logic).
139-166: LGTM!Both
BeadsValidationResultandBeadsStatsinterfaces are well-designed with clear documentation and appropriate field types for their intended purposes.
| ``` | ||
| 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. | ||
| ``` |
There was a problem hiding this comment.
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 bugOr 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.
| import { execFile } from 'child_process'; | ||
| import { promisify } from 'util'; | ||
| import path from 'path'; | ||
| import fs from 'fs/promises'; | ||
|
|
||
| const execFileAsync = promisify(execFile); |
There was a problem hiding this comment.
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.
| 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.
| 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}`); | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| 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.
| 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}`); | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| 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.
| 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; | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| 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 () => {}; | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, check the imports at the top of the file
head -20 apps/server/src/services/beads-service.tsRepository: 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.tsRepository: 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.tsRepository: 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 -20Repository: 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.tsRepository: 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.
| const { columnIssuesMap, stats } = useBeadsColumnIssues({ | ||
| issues, | ||
| searchQuery, | ||
| currentProject, | ||
| }); |
There was a problem hiding this comment.
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.
| const handleCreateFromDialog = useCallback( | ||
| async (input: any) => { | ||
| const result = await handleCreateIssue(input); | ||
| return result !== null; | ||
| }, | ||
| [handleCreateIssue] | ||
| ); |
There was a problem hiding this comment.
🛠️ 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.
| 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}> |
There was a problem hiding this comment.
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.
| 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; | ||
| }; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
💡 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".
| const { columnIssuesMap, stats } = useBeadsColumnIssues({ | ||
| issues, | ||
| searchQuery, | ||
| currentProject, | ||
| }); |
There was a problem hiding this comment.
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
Merged PR #10 - Full Kanban board UI implementation for Beads issue tracking
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
Test plan
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Chores
✏️ Tip: You can customize this high-level summary in your review settings.