beads-kanban-ui 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -222
- package/package.json +18 -55
- package/.designs/beads-kanban-ui-bj0.md +0 -73
- package/.designs/beads-kanban-ui-qxq.md +0 -144
- package/.designs/epic-support.md +0 -282
- package/.env.local.example +0 -2
- package/.eslintrc.json +0 -3
- package/.gitattributes +0 -3
- package/.github/workflows/release.yml +0 -123
- package/.history/README_20260121193710.md +0 -227
- package/.history/README_20260121193918.md +0 -227
- package/.history/README_20260121193921.md +0 -227
- package/.history/README_20260121193933.md +0 -227
- package/.history/README_20260121193934.md +0 -227
- package/.history/README_20260121193944.md +0 -227
- package/.history/README_20260121193953.md +0 -227
- package/.history/src/app/page_20260121133429.tsx +0 -134
- package/.history/src/app/page_20260121133928.tsx +0 -134
- package/.history/src/app/page_20260121144850.tsx +0 -138
- package/.history/src/app/page_20260121144854.tsx +0 -138
- package/.history/src/app/page_20260121144858.tsx +0 -138
- package/.history/src/app/page_20260121144902.tsx +0 -138
- package/.history/src/app/page_20260121144906.tsx +0 -138
- package/.history/src/app/page_20260121144911.tsx +0 -138
- package/.history/src/app/page_20260121144928.tsx +0 -138
- package/.playwright-mcp/.playwright-mcp/morphing-dialog-wheel-scroll-fix.png +0 -0
- package/.playwright-mcp/beams-test.png +0 -0
- package/.playwright-mcp/card-verification.png +0 -0
- package/.playwright-mcp/design-doc-dialog-fix-verification.png +0 -0
- package/.playwright-mcp/dialog-width-test.png +0 -0
- package/.playwright-mcp/homepage.png +0 -0
- package/.playwright-mcp/morphing-dialog-expanded.png +0 -0
- package/.playwright-mcp/morphing-dialog-fixes-final.png +0 -0
- package/.playwright-mcp/morphing-dialog-open.png +0 -0
- package/.playwright-mcp/page-2026-01-21T14-08-31-529Z.png +0 -0
- package/.playwright-mcp/page-2026-01-21T14-09-23-431Z.png +0 -0
- package/.playwright-mcp/page-2026-01-21T14-10-28-773Z.png +0 -0
- package/.playwright-mcp/page-2026-01-21T14-10-47-432Z.png +0 -0
- package/.playwright-mcp/page-2026-01-21T14-11-12-350Z.png +0 -0
- package/.playwright-mcp/screenshot-after-click.png +0 -0
- package/.playwright-mcp/screenshot-after-dialog-click.png +0 -0
- package/.playwright-mcp/sheet-restored-after-dialog-close.png +0 -0
- package/.playwright-mcp/test-1-sheet-open-with-overlay.png +0 -0
- package/.playwright-mcp/test-2-morphing-dialog-with-overlay.png +0 -0
- package/.playwright-mcp/test-3-sheet-open-dark-overlay.png +0 -0
- package/.playwright-mcp/test-4-morphing-dialog-with-dark-overlay.png +0 -0
- package/.playwright-mcp/test-5-morphing-dialog-scrolled.png +0 -0
- package/.playwright-mcp/test-6-sheet-restored-after-dialog-close.png +0 -0
- package/.playwright-mcp/wheel-scroll-fixed.png +0 -0
- package/Screenshots/bead-detail.png +0 -0
- package/Screenshots/dashboard.png +0 -0
- package/Screenshots/kanban-board.png +0 -0
- package/components.json +0 -27
- package/logo/logo.svg +0 -1
- package/next.config.js +0 -9
- package/npm/README.md +0 -37
- package/npm/package.json +0 -20
- package/postcss.config.js +0 -6
- package/public/logo.svg +0 -1
- package/restart.sh +0 -5
- package/server/Cargo.lock +0 -1685
- package/server/Cargo.toml +0 -24
- package/server/src/db.rs +0 -570
- package/server/src/main.rs +0 -141
- package/server/src/routes/beads.rs +0 -413
- package/server/src/routes/cli.rs +0 -150
- package/server/src/routes/fs.rs +0 -360
- package/server/src/routes/git.rs +0 -169
- package/server/src/routes/mod.rs +0 -107
- package/server/src/routes/projects.rs +0 -177
- package/server/src/routes/watch.rs +0 -211
- package/src/app/globals.css +0 -101
- package/src/app/layout.tsx +0 -36
- package/src/app/page.tsx +0 -348
- package/src/app/project/kanban-board.tsx +0 -356
- package/src/app/project/page.tsx +0 -18
- package/src/app/settings/page.tsx +0 -224
- package/src/components/Beams.css +0 -5
- package/src/components/Beams.jsx +0 -307
- package/src/components/Galaxy.css +0 -5
- package/src/components/Galaxy.jsx +0 -333
- package/src/components/activity-timeline.tsx +0 -172
- package/src/components/add-project-dialog.tsx +0 -219
- package/src/components/bead-card.tsx +0 -196
- package/src/components/bead-detail.tsx +0 -306
- package/src/components/color-picker.tsx +0 -101
- package/src/components/comment-input.tsx +0 -155
- package/src/components/comment-list.tsx +0 -147
- package/src/components/dependency-badge.tsx +0 -106
- package/src/components/design-doc-dialog.tsx +0 -58
- package/src/components/design-doc-preview.tsx +0 -97
- package/src/components/design-doc-viewer.tsx +0 -199
- package/src/components/editable-project-name.tsx +0 -178
- package/src/components/epic-card.tsx +0 -263
- package/src/components/folder-browser.tsx +0 -273
- package/src/components/footer.tsx +0 -27
- package/src/components/kanban/default.tsx +0 -184
- package/src/components/kanban-column.tsx +0 -167
- package/src/components/project-card.tsx +0 -191
- package/src/components/quick-filter-bar.tsx +0 -279
- package/src/components/scan-directory-dialog.tsx +0 -368
- package/src/components/status-donut.tsx +0 -197
- package/src/components/subtask-list.tsx +0 -128
- package/src/components/tag-picker.tsx +0 -252
- package/src/components/ui/.gitkeep +0 -0
- package/src/components/ui/alert-dialog.tsx +0 -141
- package/src/components/ui/avatar.tsx +0 -67
- package/src/components/ui/badge.tsx +0 -230
- package/src/components/ui/button.tsx +0 -433
- package/src/components/ui/card/index.tsx +0 -24
- package/src/components/ui/card/roiui-card.module.css +0 -197
- package/src/components/ui/card/roiui-card.tsx +0 -154
- package/src/components/ui/card/shadcn-card.tsx +0 -76
- package/src/components/ui/chart.tsx +0 -369
- package/src/components/ui/dialog.tsx +0 -122
- package/src/components/ui/dropdown-menu.tsx +0 -201
- package/src/components/ui/input.tsx +0 -22
- package/src/components/ui/kanban.tsx +0 -522
- package/src/components/ui/morphing-dialog.tsx +0 -457
- package/src/components/ui/popover.tsx +0 -33
- package/src/components/ui/progress.tsx +0 -28
- package/src/components/ui/scroll-area.tsx +0 -48
- package/src/components/ui/select.tsx +0 -159
- package/src/components/ui/separator.tsx +0 -31
- package/src/components/ui/sheet.tsx +0 -142
- package/src/components/ui/skeleton.tsx +0 -15
- package/src/components/ui/toast.tsx +0 -129
- package/src/components/ui/toaster.tsx +0 -35
- package/src/components/ui/tooltip.tsx +0 -30
- package/src/hooks/.gitkeep +0 -0
- package/src/hooks/use-bead-filters.ts +0 -261
- package/src/hooks/use-beads.ts +0 -162
- package/src/hooks/use-branch-statuses.ts +0 -161
- package/src/hooks/use-epics.ts +0 -173
- package/src/hooks/use-file-watcher.ts +0 -111
- package/src/hooks/use-keyboard-navigation.ts +0 -282
- package/src/hooks/use-project.ts +0 -61
- package/src/hooks/use-projects.ts +0 -93
- package/src/hooks/use-toast.ts +0 -194
- package/src/hooks/useClickOutside.tsx +0 -26
- package/src/lib/.gitkeep +0 -0
- package/src/lib/api.ts +0 -186
- package/src/lib/beads-parser.ts +0 -252
- package/src/lib/cli.ts +0 -193
- package/src/lib/db.ts +0 -145
- package/src/lib/design-doc.ts +0 -74
- package/src/lib/epic-parser.ts +0 -242
- package/src/lib/git.ts +0 -102
- package/src/lib/utils.ts +0 -12
- package/src/types/index.ts +0 -107
- package/tailwind.config.ts +0 -85
- package/tsconfig.json +0 -26
- /package/{npm/bin → bin}/cli.js +0 -0
- /package/{npm/scripts → scripts}/postinstall.js +0 -0
package/src/lib/beads-parser.ts
DELETED
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Parser for beads data via HTTP API
|
|
3
|
-
*
|
|
4
|
-
* Fetches and provides typed access to beads with helper functions for
|
|
5
|
-
* common operations.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import * as api from './api';
|
|
9
|
-
import type { Bead, BeadStatus, Epic } from "@/types";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Loads beads from a project directory via API
|
|
13
|
-
*
|
|
14
|
-
* @param projectPath - The root path of the project
|
|
15
|
-
* @returns Promise resolving to array of Bead objects
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* ```typescript
|
|
19
|
-
* const beads = await loadProjectBeads('/path/to/project');
|
|
20
|
-
* ```
|
|
21
|
-
*/
|
|
22
|
-
export async function loadProjectBeads(projectPath: string): Promise<Bead[]> {
|
|
23
|
-
try {
|
|
24
|
-
const result = await api.beads.read(projectPath);
|
|
25
|
-
// Ensure every bead has a comments array (defensive against null/undefined)
|
|
26
|
-
return result.beads.map((bead) => ({
|
|
27
|
-
...bead,
|
|
28
|
-
comments: bead.comments ?? [],
|
|
29
|
-
}));
|
|
30
|
-
} catch (error) {
|
|
31
|
-
console.error(`Failed to load beads from ${projectPath}:`, error);
|
|
32
|
-
return [];
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Alias for loadProjectBeads for backward compatibility
|
|
38
|
-
*/
|
|
39
|
-
export async function parseBeadsFromPath(projectPath: string): Promise<Bead[]> {
|
|
40
|
-
return loadProjectBeads(projectPath);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Groups beads by their status into a record
|
|
45
|
-
*
|
|
46
|
-
* @param beads - Array of Bead objects to group
|
|
47
|
-
* @returns Record with status keys and arrays of beads as values
|
|
48
|
-
*
|
|
49
|
-
* @example
|
|
50
|
-
* ```typescript
|
|
51
|
-
* const grouped = groupBeadsByStatus(beads);
|
|
52
|
-
* console.log(grouped.open.length); // Number of open beads
|
|
53
|
-
* console.log(grouped.closed.length); // Number of closed beads
|
|
54
|
-
* ```
|
|
55
|
-
*/
|
|
56
|
-
export function groupBeadsByStatus(beads: Bead[]): Record<BeadStatus, Bead[]> {
|
|
57
|
-
const grouped: Record<BeadStatus, Bead[]> = {
|
|
58
|
-
open: [],
|
|
59
|
-
in_progress: [],
|
|
60
|
-
inreview: [],
|
|
61
|
-
closed: [],
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
for (const bead of beads) {
|
|
65
|
-
grouped[bead.status].push(bead);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Sort each group by updated_at descending (most recent first)
|
|
69
|
-
for (const status of Object.keys(grouped) as BeadStatus[]) {
|
|
70
|
-
grouped[status].sort((a, b) => {
|
|
71
|
-
const dateA = new Date(a.updated_at).getTime();
|
|
72
|
-
const dateB = new Date(b.updated_at).getTime();
|
|
73
|
-
return dateB - dateA;
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return grouped;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Finds a bead by its ID
|
|
82
|
-
*
|
|
83
|
-
* @param beads - Array of Bead objects to search
|
|
84
|
-
* @param id - The bead ID to find
|
|
85
|
-
* @returns The matching Bead or undefined if not found
|
|
86
|
-
*
|
|
87
|
-
* @example
|
|
88
|
-
* ```typescript
|
|
89
|
-
* const bead = getBeadById(beads, 'beads-kanban-ui-323');
|
|
90
|
-
* if (bead) {
|
|
91
|
-
* console.log(bead.title);
|
|
92
|
-
* }
|
|
93
|
-
* ```
|
|
94
|
-
*/
|
|
95
|
-
export function getBeadById(beads: Bead[], id: string): Bead | undefined {
|
|
96
|
-
return beads.find((bead) => bead.id === id);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Constructs the path to issues.jsonl from a project path
|
|
101
|
-
*
|
|
102
|
-
* @param projectPath - The root path of the project
|
|
103
|
-
* @returns Path to the issues.jsonl file
|
|
104
|
-
*/
|
|
105
|
-
export function getBeadsFilePath(projectPath: string): string {
|
|
106
|
-
// Normalize path separators and ensure no trailing slash
|
|
107
|
-
const normalizedPath = projectPath.replace(/\\/g, "/").replace(/\/$/, "");
|
|
108
|
-
return `${normalizedPath}/.beads/issues.jsonl`;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Assigns sequential ticket numbers to beads based on creation order
|
|
113
|
-
*
|
|
114
|
-
* @param beads - Array of Bead objects to assign numbers to
|
|
115
|
-
* @returns Map of bead ID to ticket number (1-indexed, oldest bead = #1)
|
|
116
|
-
*
|
|
117
|
-
* @example
|
|
118
|
-
* ```typescript
|
|
119
|
-
* const ticketNumbers = assignTicketNumbers(beads);
|
|
120
|
-
* const ticketNum = ticketNumbers.get('beads-kanban-ui-323'); // e.g., 5
|
|
121
|
-
* console.log(`#${ticketNum}`); // "#5"
|
|
122
|
-
* ```
|
|
123
|
-
*/
|
|
124
|
-
export function assignTicketNumbers(beads: Bead[]): Map<string, number> {
|
|
125
|
-
// Sort all beads by created_at ascending (oldest first)
|
|
126
|
-
const sortedBeads = [...beads].sort((a, b) => {
|
|
127
|
-
const dateA = new Date(a.created_at).getTime();
|
|
128
|
-
const dateB = new Date(b.created_at).getTime();
|
|
129
|
-
return dateA - dateB;
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
// Assign 1-indexed ticket numbers
|
|
133
|
-
const ticketNumbers = new Map<string, number>();
|
|
134
|
-
sortedBeads.forEach((bead, index) => {
|
|
135
|
-
ticketNumbers.set(bead.id, index + 1);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
return ticketNumbers;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Groups beads by epic status for epic-specific views
|
|
143
|
-
*
|
|
144
|
-
* @param beads - Array of Bead objects to group
|
|
145
|
-
* @returns Record with epic status keys (with_children, standalone) and arrays of beads
|
|
146
|
-
*
|
|
147
|
-
* @example
|
|
148
|
-
* ```typescript
|
|
149
|
-
* const grouped = groupByEpicStatus(beads);
|
|
150
|
-
* console.log(grouped.epics.length); // Number of epic beads
|
|
151
|
-
* console.log(grouped.standalone.length); // Number of standalone task beads
|
|
152
|
-
* ```
|
|
153
|
-
*/
|
|
154
|
-
export function groupByEpicStatus(beads: Bead[]): {
|
|
155
|
-
epics: Epic[];
|
|
156
|
-
standalone: Bead[];
|
|
157
|
-
children: Bead[];
|
|
158
|
-
} {
|
|
159
|
-
const epics: Epic[] = [];
|
|
160
|
-
const standalone: Bead[] = [];
|
|
161
|
-
const children: Bead[] = [];
|
|
162
|
-
|
|
163
|
-
for (const bead of beads) {
|
|
164
|
-
// Epic: has issue_type 'epic' or has children
|
|
165
|
-
if (bead.issue_type === 'epic' || (bead.children && bead.children.length > 0)) {
|
|
166
|
-
epics.push({
|
|
167
|
-
...bead,
|
|
168
|
-
issue_type: 'epic',
|
|
169
|
-
children: bead.children ?? [],
|
|
170
|
-
} as Epic);
|
|
171
|
-
}
|
|
172
|
-
// Child: has parent_id
|
|
173
|
-
else if (bead.parent_id) {
|
|
174
|
-
children.push(bead);
|
|
175
|
-
}
|
|
176
|
-
// Standalone: no parent, not an epic
|
|
177
|
-
else {
|
|
178
|
-
standalone.push(bead);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return { epics, standalone, children };
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Gets all child beads for a specific epic
|
|
187
|
-
*
|
|
188
|
-
* @param epicId - The ID of the epic to get children for
|
|
189
|
-
* @param beads - Array of all beads to search
|
|
190
|
-
* @returns Array of child beads belonging to the epic
|
|
191
|
-
*
|
|
192
|
-
* @example
|
|
193
|
-
* ```typescript
|
|
194
|
-
* const children = getEpicChildren('epic-123', allBeads);
|
|
195
|
-
* console.log(`Epic has ${children.length} children`);
|
|
196
|
-
* ```
|
|
197
|
-
*/
|
|
198
|
-
export function getEpicChildren(epicId: string, beads: Bead[]): Bead[] {
|
|
199
|
-
if (!epicId || !beads || beads.length === 0) {
|
|
200
|
-
return [];
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Find the epic first
|
|
204
|
-
const epic = beads.find((b) => b.id === epicId);
|
|
205
|
-
if (!epic || !epic.children || epic.children.length === 0) {
|
|
206
|
-
return [];
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Create a lookup map for fast access
|
|
210
|
-
const beadMap = new Map<string, Bead>();
|
|
211
|
-
for (const bead of beads) {
|
|
212
|
-
beadMap.set(bead.id, bead);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Resolve children
|
|
216
|
-
return epic.children
|
|
217
|
-
.map((childId) => beadMap.get(childId))
|
|
218
|
-
.filter((child): child is Bead => child !== undefined);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Checks if an epic is completed (all children closed)
|
|
223
|
-
*
|
|
224
|
-
* @param epic - The epic bead to check
|
|
225
|
-
* @param beads - Array of all beads to resolve children from
|
|
226
|
-
* @returns True if all children are closed, false otherwise
|
|
227
|
-
*
|
|
228
|
-
* @example
|
|
229
|
-
* ```typescript
|
|
230
|
-
* if (isEpicCompleted(epic, allBeads)) {
|
|
231
|
-
* console.log('Epic is fully completed!');
|
|
232
|
-
* }
|
|
233
|
-
* ```
|
|
234
|
-
*/
|
|
235
|
-
export function isEpicCompleted(epic: Epic, beads: Bead[]): boolean {
|
|
236
|
-
if (!epic.children || epic.children.length === 0) {
|
|
237
|
-
// Epic with no children is considered completed
|
|
238
|
-
return true;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (!beads || beads.length === 0) {
|
|
242
|
-
return false;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const children = getEpicChildren(epic.id, beads);
|
|
246
|
-
if (children.length === 0) {
|
|
247
|
-
return false;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// All children must be closed
|
|
251
|
-
return children.every((child) => child.status === 'closed');
|
|
252
|
-
}
|
package/src/lib/cli.ts
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI wrapper for bd (beads) commands via HTTP API
|
|
3
|
-
*
|
|
4
|
-
* Provides typed async functions for interacting with the bd CLI tool
|
|
5
|
-
* through the backend API.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import * as api from './api';
|
|
9
|
-
import type { BeadStatus } from "@/types";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Result from executing a CLI command
|
|
13
|
-
*/
|
|
14
|
-
export interface CommandResult {
|
|
15
|
-
success: boolean;
|
|
16
|
-
stdout: string;
|
|
17
|
-
stderr: string;
|
|
18
|
-
code: number | null;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Execute a bd CLI command with the given arguments
|
|
23
|
-
*
|
|
24
|
-
* @param args - Array of command arguments (excluding 'bd')
|
|
25
|
-
* @param cwd - Working directory for the command
|
|
26
|
-
* @returns Promise resolving to command result
|
|
27
|
-
*/
|
|
28
|
-
async function executeBdCommand(
|
|
29
|
-
args: string[],
|
|
30
|
-
cwd?: string
|
|
31
|
-
): Promise<CommandResult> {
|
|
32
|
-
const result = await api.bd.command(args, cwd);
|
|
33
|
-
|
|
34
|
-
return {
|
|
35
|
-
success: result.code === 0,
|
|
36
|
-
stdout: result.stdout,
|
|
37
|
-
stderr: result.stderr,
|
|
38
|
-
code: result.code,
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Add a comment to a bead
|
|
44
|
-
*
|
|
45
|
-
* Executes: bd comment <beadId> "<message>"
|
|
46
|
-
*
|
|
47
|
-
* @param beadId - The ID of the bead to comment on
|
|
48
|
-
* @param message - The comment message
|
|
49
|
-
* @param cwd - Working directory (project path)
|
|
50
|
-
* @throws Error if command fails
|
|
51
|
-
*
|
|
52
|
-
* @example
|
|
53
|
-
* ```typescript
|
|
54
|
-
* await addComment('BD-001', 'Fixed the bug', '/path/to/project');
|
|
55
|
-
* ```
|
|
56
|
-
*/
|
|
57
|
-
export async function addComment(
|
|
58
|
-
beadId: string,
|
|
59
|
-
message: string,
|
|
60
|
-
cwd?: string
|
|
61
|
-
): Promise<void> {
|
|
62
|
-
const result = await executeBdCommand(["comment", beadId, message], cwd);
|
|
63
|
-
|
|
64
|
-
if (!result.success) {
|
|
65
|
-
throw new Error(result.stderr || `Failed to add comment: exit code ${result.code}`);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Update the status of a bead
|
|
71
|
-
*
|
|
72
|
-
* Executes: bd update <beadId> --status <status>
|
|
73
|
-
*
|
|
74
|
-
* @param beadId - The ID of the bead to update
|
|
75
|
-
* @param status - The new status value
|
|
76
|
-
* @param cwd - Working directory (project path)
|
|
77
|
-
* @throws Error if command fails
|
|
78
|
-
*
|
|
79
|
-
* @example
|
|
80
|
-
* ```typescript
|
|
81
|
-
* await updateStatus('BD-001', 'in_progress', '/path/to/project');
|
|
82
|
-
* ```
|
|
83
|
-
*/
|
|
84
|
-
export async function updateStatus(
|
|
85
|
-
beadId: string,
|
|
86
|
-
status: BeadStatus,
|
|
87
|
-
cwd?: string
|
|
88
|
-
): Promise<void> {
|
|
89
|
-
const result = await executeBdCommand(
|
|
90
|
-
["update", beadId, "--status", status],
|
|
91
|
-
cwd
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
if (!result.success) {
|
|
95
|
-
throw new Error(result.stderr || `Failed to update status: exit code ${result.code}`);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Close a bead
|
|
101
|
-
*
|
|
102
|
-
* Executes: bd close <beadId>
|
|
103
|
-
*
|
|
104
|
-
* @param beadId - The ID of the bead to close
|
|
105
|
-
* @param cwd - Working directory (project path)
|
|
106
|
-
* @throws Error if command fails
|
|
107
|
-
*
|
|
108
|
-
* @example
|
|
109
|
-
* ```typescript
|
|
110
|
-
* await closeBead('BD-001', '/path/to/project');
|
|
111
|
-
* ```
|
|
112
|
-
*/
|
|
113
|
-
export async function closeBead(beadId: string, cwd?: string): Promise<void> {
|
|
114
|
-
const result = await executeBdCommand(["close", beadId], cwd);
|
|
115
|
-
|
|
116
|
-
if (!result.success) {
|
|
117
|
-
throw new Error(result.stderr || `Failed to close bead: exit code ${result.code}`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Create a new bead
|
|
123
|
-
*
|
|
124
|
-
* Executes: bd create "<title>" -d "<description>"
|
|
125
|
-
*
|
|
126
|
-
* @param title - The bead title
|
|
127
|
-
* @param description - The bead description
|
|
128
|
-
* @param cwd - Working directory (project path)
|
|
129
|
-
* @returns The created bead ID (if parseable from output)
|
|
130
|
-
* @throws Error if command fails
|
|
131
|
-
*
|
|
132
|
-
* @example
|
|
133
|
-
* ```typescript
|
|
134
|
-
* const id = await createBead('Fix bug', 'Bug in login form', '/path/to/project');
|
|
135
|
-
* ```
|
|
136
|
-
*/
|
|
137
|
-
export async function createBead(
|
|
138
|
-
title: string,
|
|
139
|
-
description: string,
|
|
140
|
-
cwd?: string
|
|
141
|
-
): Promise<string | null> {
|
|
142
|
-
const result = await executeBdCommand(
|
|
143
|
-
["create", title, "-d", description],
|
|
144
|
-
cwd
|
|
145
|
-
);
|
|
146
|
-
|
|
147
|
-
if (!result.success) {
|
|
148
|
-
throw new Error(result.stderr || `Failed to create bead: exit code ${result.code}`);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Try to extract bead ID from output (format varies by CLI version)
|
|
152
|
-
const idMatch = result.stdout.match(/(?:BD-|bd-)[\w-]+/i);
|
|
153
|
-
return idMatch ? idMatch[0] : null;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Get bead details
|
|
158
|
-
*
|
|
159
|
-
* Executes: bd show <beadId>
|
|
160
|
-
*
|
|
161
|
-
* @param beadId - The ID of the bead to show
|
|
162
|
-
* @param cwd - Working directory (project path)
|
|
163
|
-
* @returns Raw output from bd show command
|
|
164
|
-
* @throws Error if command fails
|
|
165
|
-
*/
|
|
166
|
-
export async function showBead(beadId: string, cwd?: string): Promise<string> {
|
|
167
|
-
const result = await executeBdCommand(["show", beadId], cwd);
|
|
168
|
-
|
|
169
|
-
if (!result.success) {
|
|
170
|
-
throw new Error(result.stderr || `Failed to show bead: exit code ${result.code}`);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return result.stdout;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* List all beads
|
|
178
|
-
*
|
|
179
|
-
* Executes: bd list
|
|
180
|
-
*
|
|
181
|
-
* @param cwd - Working directory (project path)
|
|
182
|
-
* @returns Raw output from bd list command
|
|
183
|
-
* @throws Error if command fails
|
|
184
|
-
*/
|
|
185
|
-
export async function listBeads(cwd?: string): Promise<string> {
|
|
186
|
-
const result = await executeBdCommand(["list"], cwd);
|
|
187
|
-
|
|
188
|
-
if (!result.success) {
|
|
189
|
-
throw new Error(result.stderr || `Failed to list beads: exit code ${result.code}`);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return result.stdout;
|
|
193
|
-
}
|
package/src/lib/db.ts
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Database client for HTTP API operations
|
|
3
|
-
*
|
|
4
|
-
* Provides typed wrappers around API calls for projects, tags,
|
|
5
|
-
* and their relationships.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import * as api from './api';
|
|
9
|
-
import type { Project, Tag } from '@/types';
|
|
10
|
-
|
|
11
|
-
// ===== Input Types =====
|
|
12
|
-
|
|
13
|
-
export type { Project, Tag };
|
|
14
|
-
|
|
15
|
-
export interface CreateProjectInput {
|
|
16
|
-
name: string;
|
|
17
|
-
path: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface UpdateProjectInput {
|
|
21
|
-
id: string;
|
|
22
|
-
name?: string;
|
|
23
|
-
path?: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface CreateTagInput {
|
|
27
|
-
name: string;
|
|
28
|
-
color: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface UpdateTagInput {
|
|
32
|
-
id: string;
|
|
33
|
-
name?: string;
|
|
34
|
-
color?: string;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// ===== Project Operations =====
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Gets all projects, ordered by last opened
|
|
41
|
-
*/
|
|
42
|
-
export async function getProjects(): Promise<Project[]> {
|
|
43
|
-
return api.projects.list();
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Creates a new project
|
|
48
|
-
*/
|
|
49
|
-
export async function createProject(input: CreateProjectInput): Promise<Project> {
|
|
50
|
-
return api.projects.create(input);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Updates an existing project
|
|
55
|
-
*/
|
|
56
|
-
export async function updateProject(input: UpdateProjectInput): Promise<Project> {
|
|
57
|
-
const { id, ...data } = input;
|
|
58
|
-
return api.projects.update(id, data);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Deletes a project by ID
|
|
63
|
-
*/
|
|
64
|
-
export async function deleteProject(id: string): Promise<void> {
|
|
65
|
-
return api.projects.delete(id);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// ===== Tag Operations =====
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Gets all tags
|
|
72
|
-
*/
|
|
73
|
-
export async function getTags(): Promise<Tag[]> {
|
|
74
|
-
return api.tags.list();
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Creates a new tag
|
|
79
|
-
*/
|
|
80
|
-
export async function createTag(input: CreateTagInput): Promise<Tag> {
|
|
81
|
-
return api.tags.create(input);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Updates an existing tag
|
|
86
|
-
*/
|
|
87
|
-
export async function updateTag(input: UpdateTagInput): Promise<Tag> {
|
|
88
|
-
const { id, ...data } = input;
|
|
89
|
-
// Note: API doesn't have a separate update endpoint, using create for now
|
|
90
|
-
// This would need backend support for tag updates
|
|
91
|
-
return api.tags.create(data as CreateTagInput);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Deletes a tag by ID
|
|
96
|
-
*/
|
|
97
|
-
export async function deleteTag(id: string): Promise<void> {
|
|
98
|
-
return api.tags.delete(id);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// ===== Project-Tag Relationships =====
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Gets all tags for a specific project
|
|
105
|
-
*/
|
|
106
|
-
export async function getProjectTags(projectId: string): Promise<Tag[]> {
|
|
107
|
-
const projects = await api.projects.list();
|
|
108
|
-
const project = projects.find((p) => p.id === projectId);
|
|
109
|
-
return project?.tags || [];
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Adds a tag to a project
|
|
114
|
-
*/
|
|
115
|
-
export async function addTagToProject(projectId: string, tagId: string): Promise<void> {
|
|
116
|
-
return api.tags.addToProject(projectId, tagId);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Removes a tag from a project
|
|
121
|
-
*/
|
|
122
|
-
export async function removeTagFromProject(projectId: string, tagId: string): Promise<void> {
|
|
123
|
-
return api.tags.removeFromProject(projectId, tagId);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// ===== Convenience Functions =====
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Gets a project with its tags
|
|
130
|
-
*/
|
|
131
|
-
export async function getProjectWithTags(projectId: string): Promise<Project> {
|
|
132
|
-
const projects = await getProjects();
|
|
133
|
-
const project = projects.find((p) => p.id === projectId);
|
|
134
|
-
if (!project) {
|
|
135
|
-
throw new Error(`Project not found: ${projectId}`);
|
|
136
|
-
}
|
|
137
|
-
return project;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Gets all projects with their tags
|
|
142
|
-
*/
|
|
143
|
-
export async function getProjectsWithTags(): Promise<Project[]> {
|
|
144
|
-
return getProjects();
|
|
145
|
-
}
|
package/src/lib/design-doc.ts
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Design Doc Utilities
|
|
3
|
-
* Shared functions for fetching and processing design documents
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const API_BASE = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:3008';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Fetch design doc content from the backend API
|
|
10
|
-
*/
|
|
11
|
-
export async function fetchDesignDoc(path: string, projectPath: string): Promise<string> {
|
|
12
|
-
const encodedPath = encodeURIComponent(path);
|
|
13
|
-
const encodedProjectPath = encodeURIComponent(projectPath);
|
|
14
|
-
const response = await fetch(
|
|
15
|
-
`${API_BASE}/api/fs/read?path=${encodedPath}&project_path=${encodedProjectPath}`
|
|
16
|
-
);
|
|
17
|
-
if (!response.ok) {
|
|
18
|
-
throw new Error('Failed to fetch design doc: ' + response.statusText);
|
|
19
|
-
}
|
|
20
|
-
const data = await response.json();
|
|
21
|
-
return data.content || '';
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Strip markdown syntax and convert to plain text preview
|
|
26
|
-
* Removes headers, links, code blocks, bold, italic, etc.
|
|
27
|
-
*/
|
|
28
|
-
export function truncateMarkdownToPlainText(markdown: string, maxChars: number = 180): string {
|
|
29
|
-
let text = markdown;
|
|
30
|
-
|
|
31
|
-
// Remove code blocks
|
|
32
|
-
text = text.replace(/```[\s\S]*?```/g, '');
|
|
33
|
-
|
|
34
|
-
// Remove inline code
|
|
35
|
-
text = text.replace(/`[^`]+`/g, '');
|
|
36
|
-
|
|
37
|
-
// Remove headers (# ## ###)
|
|
38
|
-
text = text.replace(/^#{1,6}\s+/gm, '');
|
|
39
|
-
|
|
40
|
-
// Remove links but keep text: [text](url) -> text
|
|
41
|
-
text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1');
|
|
42
|
-
|
|
43
|
-
// Remove bold/italic: **text** or *text* -> text
|
|
44
|
-
text = text.replace(/\*\*([^*]+)\*\*/g, '$1');
|
|
45
|
-
text = text.replace(/\*([^*]+)\*/g, '$1');
|
|
46
|
-
|
|
47
|
-
// Remove blockquotes
|
|
48
|
-
text = text.replace(/^>\s+/gm, '');
|
|
49
|
-
|
|
50
|
-
// Remove horizontal rules
|
|
51
|
-
text = text.replace(/^-{3,}$/gm, '');
|
|
52
|
-
|
|
53
|
-
// Remove list markers
|
|
54
|
-
text = text.replace(/^[\s]*[-*+]\s+/gm, '');
|
|
55
|
-
text = text.replace(/^[\s]*\d+\.\s+/gm, '');
|
|
56
|
-
|
|
57
|
-
// Collapse multiple newlines
|
|
58
|
-
text = text.replace(/\n{2,}/g, ' ');
|
|
59
|
-
|
|
60
|
-
// Trim and truncate
|
|
61
|
-
text = text.trim();
|
|
62
|
-
|
|
63
|
-
if (text.length <= maxChars) {
|
|
64
|
-
return text;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return text.slice(0, maxChars).trim() + '…';
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Shared prose classes for markdown rendering
|
|
72
|
-
* Ensures consistent styling across preview and full view
|
|
73
|
-
*/
|
|
74
|
-
export const designDocProseClasses = "prose prose-sm dark:prose-invert max-w-none prose-headings:scroll-mt-20 prose-pre:bg-zinc-900 prose-pre:text-zinc-100 prose-code:text-sm prose-code:bg-zinc-100 dark:prose-code:bg-zinc-800 prose-code:px-1 prose-code:py-0.5 prose-code:rounded";
|