Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions apps/server/src/routes/beads/common.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
/**
* Common utilities for beads routes
* Derives a readable string message from an unknown error value.
*
* @param error - The error value to extract a message from.
* @returns The error's `message` if `error` is an `Error`, otherwise the stringified `error` (`String(error)`).
*/

export function getErrorMessage(error: unknown): string {
Expand All @@ -9,7 +12,15 @@ export function getErrorMessage(error: unknown): string {
return String(error);
}

/**
* Log a standardized Beads error message to the console.
*
* Extracts a readable message from `error` and writes it to stderr prefixed with a `[Beads]` tag and the provided context.
*
* @param error - The error value to derive a message from (may be any value).
* @param context - Short contextual label included in the log prefix (e.g., the route or operation name)
*/
export function logError(error: unknown, context: string): void {
const message = getErrorMessage(error);
console.error(`[Beads] ${context}:`, message);
}
}
8 changes: 7 additions & 1 deletion apps/server/src/routes/beads/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ import { createDeleteHandler } from './routes/delete.js';
import { createReadyWorkHandler } from './routes/ready.js';
import { createValidateHandler } from './routes/validate.js';

/**
* Create an Express Router configured with Beads-related POST endpoints.
*
* @param beadsService - Service used by route handlers to perform Beads operations
* @returns The configured Express Router containing the Beads endpoints (POST /list, /create, /update, /delete, /ready, and /validate)
*/
export function createBeadsRoutes(beadsService: BeadsService): Router {
const router = Router();

Expand All @@ -23,4 +29,4 @@ export function createBeadsRoutes(beadsService: BeadsService): Router {
router.post('/validate', createValidateHandler(beadsService));

return router;
}
}
12 changes: 11 additions & 1 deletion apps/server/src/routes/beads/routes/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ import type { Request, Response } from 'express';
import { BeadsService } from '../../../services/beads-service.js';
import { getErrorMessage, logError } from '../common.js';

/**
* Creates an Express request handler for creating a beads issue.
*
* The handler validates that `projectPath` and `issue.title` are present in the request body,
* calls the provided service to create the issue, and sends a JSON response:
* on success `{ success: true, issue }`, on validation failure a 400 with `{ success: false, error }`,
* and on internal error a 500 with `{ success: false, error }`.
*
* @returns An Express-compatible request handler that performs the described validation, creation, and responses.
*/
export function createCreateHandler(beadsService: BeadsService) {
return async (req: Request, res: Response): Promise<void> => {
try {
Expand Down Expand Up @@ -37,4 +47,4 @@ export function createCreateHandler(beadsService: BeadsService) {
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};
}
}
8 changes: 7 additions & 1 deletion apps/server/src/routes/beads/routes/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import type { Request, Response } from 'express';
import { BeadsService } from '../../../services/beads-service.js';
import { getErrorMessage, logError } from '../common.js';

/**
* Create an Express POST /delete handler that deletes a beads issue.
*
* @param beadsService - Service used to perform issue deletion
* @returns An Express request handler that validates `projectPath` and `issueId` from the request body, optionally accepts `force`, invokes `beadsService.deleteIssue`, responds with `{ success: true }` on success, and returns structured JSON errors with appropriate HTTP status codes on validation failure or internal errors
*/
export function createDeleteHandler(beadsService: BeadsService) {
return async (req: Request, res: Response): Promise<void> => {
try {
Expand All @@ -32,4 +38,4 @@ export function createDeleteHandler(beadsService: BeadsService) {
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};
}
}
13 changes: 12 additions & 1 deletion apps/server/src/routes/beads/routes/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@ import type { Request, Response } from 'express';
import { BeadsService } from '../../../services/beads-service.js';
import { getErrorMessage, logError } from '../common.js';

/**
* Creates an Express handler that lists bead issues for a given project.
*
* The returned async middleware expects req.body to contain `projectPath` (string) and an optional
* `filters` object with any of: `status`, `type`, `labels`, `priorityMin`, `priorityMax`,
* `titleContains`, `descContains`, and `ids`. If `projectPath` is missing the handler responds with
* HTTP 400 and an error JSON; on success it responds with `{ success: true, issues }`; on failure it
* logs the error and responds with HTTP 500 and a standardized error message.
*
* @returns An Express-compatible async middleware (req, res) that lists issues for the specified project.
*/
export function createListHandler(beadsService: BeadsService) {
return async (req: Request, res: Response): Promise<void> => {
try {
Expand Down Expand Up @@ -35,4 +46,4 @@ export function createListHandler(beadsService: BeadsService) {
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};
}
}
13 changes: 12 additions & 1 deletion apps/server/src/routes/beads/routes/ready.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@ import type { Request, Response } from 'express';
import { BeadsService } from '../../../services/beads-service.js';
import { getErrorMessage, logError } from '../common.js';

/**
* Creates an Express handler that returns "ready work" issues for a project.
*
* The handler expects `projectPath` (required) and `limit` (optional) in the request body.
* If `projectPath` is missing, it responds with HTTP 400 and an error payload.
* On success it responds with `{ success: true, issues }`. On failure it logs the error
* and responds with HTTP 500 and a user-facing error message.
*
* @param beadsService - Service used to fetch ready-work issues for a given project
* @returns An Express middleware function that processes ready-work requests
*/
export function createReadyWorkHandler(beadsService: BeadsService) {
return async (req: Request, res: Response): Promise<void> => {
try {
Expand All @@ -26,4 +37,4 @@ export function createReadyWorkHandler(beadsService: BeadsService) {
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};
}
}
8 changes: 7 additions & 1 deletion apps/server/src/routes/beads/routes/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import type { Request, Response } from 'express';
import { BeadsService } from '../../../services/beads-service.js';
import { getErrorMessage, logError } from '../common.js';

/**
* Create an Express route handler that updates an existing beads issue.
*
* @param beadsService - Service used to perform the issue update
* @returns An Express request handler that validates `projectPath`, `issueId`, and `updates` from the request body, calls the service to apply changes, and sends JSON responses: on success `{ success: true, issue }`, on validation failure a 400 with `{ success: false, error }`, and on unexpected errors a 500 with `{ success: false, error }`.
*/
export function createUpdateHandler(beadsService: BeadsService) {
return async (req: Request, res: Response): Promise<void> => {
try {
Expand Down Expand Up @@ -44,4 +50,4 @@ export function createUpdateHandler(beadsService: BeadsService) {
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};
}
}
8 changes: 7 additions & 1 deletion apps/server/src/routes/beads/routes/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import type { Request, Response } from 'express';
import { BeadsService } from '../../../services/beads-service.js';
import { getErrorMessage, logError } from '../common.js';

/**
* Creates an Express route handler that validates beads installation or a specific project and sends a JSON response.
*
* @param beadsService - Service used to check installation, get version, or validate a project
* @returns An Express request handler that responds with `{ success: true, ... }` on success (either `{ installed, version }` or the project validation result) and with HTTP 500 and `{ success: false, error }` on failure
*/
export function createValidateHandler(beadsService: BeadsService) {
return async (req: Request, res: Response): Promise<void> => {
try {
Expand All @@ -26,4 +32,4 @@ export function createValidateHandler(beadsService: BeadsService) {
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ interface UseNavigationProps {
cycleNextProject: () => void;
}

/**
* Constructs sidebar navigation sections and their keyboard shortcuts based on the provided state and visibility flags.
*
* @returns An object containing `navSections` — an array of navigation sections (labels and items) to render, and `navigationShortcuts` — an array of keyboard shortcut descriptors with keys, actions, and descriptions.
*/
export function useNavigation({
shortcuts,
hideSpecEditor,
Expand Down Expand Up @@ -271,4 +276,4 @@ export function useNavigation({
navSections,
navigationShortcuts,
};
}
}
11 changes: 10 additions & 1 deletion apps/ui/src/components/views/beads-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ import type { BeadsIssue } from '@automaker/types';
import { getElectronAPI } from '@/lib/electron';
import { toast } from 'sonner';

/**
* Render the Beads Kanban board view for the current project.
*
* Wires Beads-related hooks to load and organize issues, manages create/edit/delete dialog
* state and selected issue, handles status transitions and drag-and-drop, validates Beads
* initialization before creating issues, and displays loading, error, or no-project states.
*
* @returns A React element containing the Beads Kanban board, header, and dialogs.
*/
export function BeadsView() {
const { currentProject } = useAppStore();

Expand Down Expand Up @@ -244,4 +253,4 @@ export function BeadsView() {
/>
</div>
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ interface TypeBadgeProps {
className?: string;
}

/**
* Renders a compact badge that displays the issue type with its associated colors.
*
* @param type - The issue type text to display; color styling is derived from TYPE_COLORS with a fallback to the `task` color.
* @param className - Optional additional CSS classes to apply to the badge container.
* @returns A span element styled as a small badge showing the provided `type`.
*/
export function TypeBadge({ type, className }: TypeBadgeProps) {
const colors = TYPE_COLORS[type] || TYPE_COLORS.task;

Expand All @@ -28,6 +35,17 @@ interface PriorityIndicatorProps {
className?: string;
}

/**
* Render a compact priority indicator with a colored dot and uppercase label.
*
* Displays a small colored dot and the priority label (uppercase). The displayed
* color and label are derived from the provided `priority` using the module's
* priority mappings. Additional CSS classes can be appended via `className`.
*
* @param priority - Issue priority key used to select the label and colors
* @param className - Optional additional class names to apply to the container
* @returns A JSX element showing the colored dot and priority label
*/
export function PriorityIndicator({ priority, className }: PriorityIndicatorProps) {
const label = PRIORITY_LABELS[priority];
const colors = PRIORITY_COLORS[priority];
Expand All @@ -45,6 +63,15 @@ interface BlockingBadgeProps {
className?: string;
}

/**
* Render a compact "Blocks N" badge for an issue when it blocks other issues.
*
* Renders nothing when `count` is 0.
*
* @param count - Number of issues this issue blocks; when `0` the component returns `null`
* @param className - Optional additional CSS class names to apply to the badge
* @returns A `span` element displaying "Blocks {count}", or `null` if `count` is `0`
*/
export function BlockingBadge({ count, className }: BlockingBadgeProps) {
if (count === 0) return null;

Expand All @@ -67,6 +94,15 @@ interface BlockedBadgeProps {
className?: string;
}

/**
* Render a compact "Blocked by N" badge when an issue is blocked by one or more other issues.
*
* The component returns `null` when `count` is zero.
*
* @param count - Number of issues blocking this issue; determines the displayed count and whether the badge is rendered
* @param className - Additional CSS classes to apply to the badge container
* @returns A span element displaying "Blocked by {count}" when `count` > 0, `null` otherwise
*/
export function BlockedBadge({ count, className }: BlockedBadgeProps) {
if (count === 0) return null;

Expand All @@ -89,6 +125,13 @@ interface LabelsListProps {
className?: string;
}

/**
* Render a wrapped list of label chips from an array of strings.
*
* @param labels - Array of label text to display; if empty or missing, nothing is rendered
* @param className - Optional additional CSS class names applied to the container
* @returns A container of styled label chips for each string in `labels`, or `null` when `labels` is empty
*/
export function LabelsList({ labels, className }: LabelsListProps) {
if (!labels || labels.length === 0) return null;

Expand All @@ -104,4 +147,4 @@ export function LabelsList({ labels, className }: LabelsListProps) {
))}
</div>
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ const PRIORITY_OPTIONS: { value: BeadsIssuePriority; label: string }[] = [
{ value: 4, label: 'LOWEST' },
];

/**
* Modal dialog UI for creating a new Beads issue with title, description, type, priority and labels.
*
* @param open - Whether the dialog is visible
* @param onOpenChange - Callback invoked with the updated open state when the dialog should open or close
* @param onCreate - Async callback invoked with the issue input when the user submits; should resolve to `true` on successful creation
* @returns The rendered Create Issue dialog element
*/
export function CreateIssueDialog({ open, onOpenChange, onCreate }: CreateIssueDialogProps) {
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
Expand Down Expand Up @@ -212,4 +220,4 @@ export function CreateIssueDialog({ open, onOpenChange, onCreate }: CreateIssueD
</DialogContent>
</Dialog>
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,20 @@ interface DeleteIssueDialogProps {
isDeleting?: boolean;
}

/**
* Render a confirmation dialog for deleting a Beads issue.
*
* Shows the issue's title and ID, an optional warning when the issue blocks others,
* and actions to cancel or confirm deletion.
*
* @param open - Whether the dialog is visible
* @param onOpenChange - Callback invoked with the new open state
* @param issue - The issue to delete; when null nothing is shown in the dialog body
* @param blockingCount - Number of other issues blocked by this issue; used to show a warning when > 0
* @param onDelete - Async handler invoked when the user confirms deletion
* @param isDeleting - When true, disables actions and shows a "Deleting..." label on the confirm button
* @returns The dialog element for confirming and performing issue deletion
*/
export function DeleteIssueDialog({
open,
onOpenChange,
Expand Down Expand Up @@ -73,4 +87,4 @@ export function DeleteIssueDialog({
</DialogContent>
</Dialog>
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ const PRIORITY_OPTIONS: { value: BeadsIssuePriority; label: string }[] = [
{ value: 4, label: 'LOWEST' },
];

/**
* Dialog UI for editing an existing Beads issue.
*
* Preloads form fields from `issue`, lets the user edit title, description, type, priority and labels,
* validates that title is present before submitting, shows an updating state while saving, and closes the dialog when the update succeeds.
*
* @param open - Whether the dialog is visible
* @param onOpenChange - Callback invoked with the new open state
* @param issue - The current issue to edit (or `null` to show an empty/initial form)
* @param onUpdate - Async callback called with `(issueId, updates)` to persist changes; should return `true` on success
* @returns The EditIssueDialog React element
*/
export function EditIssueDialog({ open, onOpenChange, issue, onUpdate }: EditIssueDialogProps) {
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
Expand Down Expand Up @@ -217,4 +229,4 @@ export function EditIssueDialog({ open, onOpenChange, issue, onUpdate }: EditIss
</DialogContent>
</Dialog>
);
}
}
Loading