beads-kanban-ui 0.1.0 → 0.1.1
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
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useState, useEffect, useCallback } from "react";
|
|
4
|
-
import {
|
|
5
|
-
getProjectsWithTags,
|
|
6
|
-
createProject,
|
|
7
|
-
type CreateProjectInput,
|
|
8
|
-
} from "@/lib/db";
|
|
9
|
-
import { loadProjectBeads, groupBeadsByStatus } from "@/lib/beads-parser";
|
|
10
|
-
import type { Project, Tag, BeadCounts } from "@/types";
|
|
11
|
-
|
|
12
|
-
interface UseProjectsResult {
|
|
13
|
-
projects: Project[];
|
|
14
|
-
isLoading: boolean;
|
|
15
|
-
error: Error | null;
|
|
16
|
-
refetch: () => Promise<void>;
|
|
17
|
-
addProject: (input: CreateProjectInput) => Promise<Project>;
|
|
18
|
-
updateProjectTags: (projectId: string, tags: Tag[]) => void;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function useProjects(): UseProjectsResult {
|
|
22
|
-
const [projects, setProjects] = useState<Project[]>([]);
|
|
23
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
24
|
-
const [error, setError] = useState<Error | null>(null);
|
|
25
|
-
|
|
26
|
-
const fetchProjects = useCallback(async () => {
|
|
27
|
-
try {
|
|
28
|
-
setIsLoading(true);
|
|
29
|
-
setError(null);
|
|
30
|
-
const data = await getProjectsWithTags();
|
|
31
|
-
|
|
32
|
-
// Fetch bead counts for all projects in parallel
|
|
33
|
-
const projectsWithCounts = await Promise.all(
|
|
34
|
-
data.map(async (project) => {
|
|
35
|
-
try {
|
|
36
|
-
const beads = await loadProjectBeads(project.path);
|
|
37
|
-
const grouped = groupBeadsByStatus(beads);
|
|
38
|
-
const beadCounts: BeadCounts = {
|
|
39
|
-
open: grouped.open.length,
|
|
40
|
-
in_progress: grouped.in_progress.length,
|
|
41
|
-
inreview: grouped.inreview.length,
|
|
42
|
-
closed: grouped.closed.length,
|
|
43
|
-
};
|
|
44
|
-
return { ...project, beadCounts };
|
|
45
|
-
} catch {
|
|
46
|
-
// If loading beads fails, return project with zero counts
|
|
47
|
-
return {
|
|
48
|
-
...project,
|
|
49
|
-
beadCounts: { open: 0, in_progress: 0, inreview: 0, closed: 0 },
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
})
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
setProjects(projectsWithCounts);
|
|
56
|
-
} catch (err) {
|
|
57
|
-
setError(err instanceof Error ? err : new Error("Failed to fetch projects"));
|
|
58
|
-
} finally {
|
|
59
|
-
setIsLoading(false);
|
|
60
|
-
}
|
|
61
|
-
}, []);
|
|
62
|
-
|
|
63
|
-
const addProject = useCallback(
|
|
64
|
-
async (input: CreateProjectInput): Promise<Project> => {
|
|
65
|
-
const newProject = await createProject(input);
|
|
66
|
-
await fetchProjects();
|
|
67
|
-
return newProject;
|
|
68
|
-
},
|
|
69
|
-
[fetchProjects]
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
const updateProjectTags = useCallback((projectId: string, tags: Tag[]) => {
|
|
73
|
-
setProjects((prev) =>
|
|
74
|
-
prev.map((project) =>
|
|
75
|
-
project.id === projectId ? { ...project, tags } : project
|
|
76
|
-
)
|
|
77
|
-
);
|
|
78
|
-
}, []);
|
|
79
|
-
|
|
80
|
-
// Fetch projects on mount
|
|
81
|
-
useEffect(() => {
|
|
82
|
-
fetchProjects();
|
|
83
|
-
}, [fetchProjects]);
|
|
84
|
-
|
|
85
|
-
return {
|
|
86
|
-
projects,
|
|
87
|
-
isLoading,
|
|
88
|
-
error,
|
|
89
|
-
refetch: fetchProjects,
|
|
90
|
-
addProject,
|
|
91
|
-
updateProjectTags,
|
|
92
|
-
};
|
|
93
|
-
}
|
package/src/hooks/use-toast.ts
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
// Inspired by react-hot-toast library
|
|
4
|
-
import * as React from "react"
|
|
5
|
-
|
|
6
|
-
import type {
|
|
7
|
-
ToastActionElement,
|
|
8
|
-
ToastProps,
|
|
9
|
-
} from "@/components/ui/toast"
|
|
10
|
-
|
|
11
|
-
const TOAST_LIMIT = 1
|
|
12
|
-
const TOAST_REMOVE_DELAY = 1000000
|
|
13
|
-
|
|
14
|
-
type ToasterToast = ToastProps & {
|
|
15
|
-
id: string
|
|
16
|
-
title?: React.ReactNode
|
|
17
|
-
description?: React.ReactNode
|
|
18
|
-
action?: ToastActionElement
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const actionTypes = {
|
|
22
|
-
ADD_TOAST: "ADD_TOAST",
|
|
23
|
-
UPDATE_TOAST: "UPDATE_TOAST",
|
|
24
|
-
DISMISS_TOAST: "DISMISS_TOAST",
|
|
25
|
-
REMOVE_TOAST: "REMOVE_TOAST",
|
|
26
|
-
} as const
|
|
27
|
-
|
|
28
|
-
let count = 0
|
|
29
|
-
|
|
30
|
-
function genId() {
|
|
31
|
-
count = (count + 1) % Number.MAX_SAFE_INTEGER
|
|
32
|
-
return count.toString()
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
type ActionType = typeof actionTypes
|
|
36
|
-
|
|
37
|
-
type Action =
|
|
38
|
-
| {
|
|
39
|
-
type: ActionType["ADD_TOAST"]
|
|
40
|
-
toast: ToasterToast
|
|
41
|
-
}
|
|
42
|
-
| {
|
|
43
|
-
type: ActionType["UPDATE_TOAST"]
|
|
44
|
-
toast: Partial<ToasterToast>
|
|
45
|
-
}
|
|
46
|
-
| {
|
|
47
|
-
type: ActionType["DISMISS_TOAST"]
|
|
48
|
-
toastId?: ToasterToast["id"]
|
|
49
|
-
}
|
|
50
|
-
| {
|
|
51
|
-
type: ActionType["REMOVE_TOAST"]
|
|
52
|
-
toastId?: ToasterToast["id"]
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
interface State {
|
|
56
|
-
toasts: ToasterToast[]
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
|
|
60
|
-
|
|
61
|
-
const addToRemoveQueue = (toastId: string) => {
|
|
62
|
-
if (toastTimeouts.has(toastId)) {
|
|
63
|
-
return
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const timeout = setTimeout(() => {
|
|
67
|
-
toastTimeouts.delete(toastId)
|
|
68
|
-
dispatch({
|
|
69
|
-
type: "REMOVE_TOAST",
|
|
70
|
-
toastId: toastId,
|
|
71
|
-
})
|
|
72
|
-
}, TOAST_REMOVE_DELAY)
|
|
73
|
-
|
|
74
|
-
toastTimeouts.set(toastId, timeout)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export const reducer = (state: State, action: Action): State => {
|
|
78
|
-
switch (action.type) {
|
|
79
|
-
case "ADD_TOAST":
|
|
80
|
-
return {
|
|
81
|
-
...state,
|
|
82
|
-
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
case "UPDATE_TOAST":
|
|
86
|
-
return {
|
|
87
|
-
...state,
|
|
88
|
-
toasts: state.toasts.map((t) =>
|
|
89
|
-
t.id === action.toast.id ? { ...t, ...action.toast } : t
|
|
90
|
-
),
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
case "DISMISS_TOAST": {
|
|
94
|
-
const { toastId } = action
|
|
95
|
-
|
|
96
|
-
// ! Side effects ! - This could be extracted into a dismissToast() action,
|
|
97
|
-
// but I'll keep it here for simplicity
|
|
98
|
-
if (toastId) {
|
|
99
|
-
addToRemoveQueue(toastId)
|
|
100
|
-
} else {
|
|
101
|
-
state.toasts.forEach((toast) => {
|
|
102
|
-
addToRemoveQueue(toast.id)
|
|
103
|
-
})
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return {
|
|
107
|
-
...state,
|
|
108
|
-
toasts: state.toasts.map((t) =>
|
|
109
|
-
t.id === toastId || toastId === undefined
|
|
110
|
-
? {
|
|
111
|
-
...t,
|
|
112
|
-
open: false,
|
|
113
|
-
}
|
|
114
|
-
: t
|
|
115
|
-
),
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
case "REMOVE_TOAST":
|
|
119
|
-
if (action.toastId === undefined) {
|
|
120
|
-
return {
|
|
121
|
-
...state,
|
|
122
|
-
toasts: [],
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
return {
|
|
126
|
-
...state,
|
|
127
|
-
toasts: state.toasts.filter((t) => t.id !== action.toastId),
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const listeners: Array<(state: State) => void> = []
|
|
133
|
-
|
|
134
|
-
let memoryState: State = { toasts: [] }
|
|
135
|
-
|
|
136
|
-
function dispatch(action: Action) {
|
|
137
|
-
memoryState = reducer(memoryState, action)
|
|
138
|
-
listeners.forEach((listener) => {
|
|
139
|
-
listener(memoryState)
|
|
140
|
-
})
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
type Toast = Omit<ToasterToast, "id">
|
|
144
|
-
|
|
145
|
-
function toast({ ...props }: Toast) {
|
|
146
|
-
const id = genId()
|
|
147
|
-
|
|
148
|
-
const update = (props: ToasterToast) =>
|
|
149
|
-
dispatch({
|
|
150
|
-
type: "UPDATE_TOAST",
|
|
151
|
-
toast: { ...props, id },
|
|
152
|
-
})
|
|
153
|
-
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
|
|
154
|
-
|
|
155
|
-
dispatch({
|
|
156
|
-
type: "ADD_TOAST",
|
|
157
|
-
toast: {
|
|
158
|
-
...props,
|
|
159
|
-
id,
|
|
160
|
-
open: true,
|
|
161
|
-
onOpenChange: (open) => {
|
|
162
|
-
if (!open) dismiss()
|
|
163
|
-
},
|
|
164
|
-
},
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
return {
|
|
168
|
-
id: id,
|
|
169
|
-
dismiss,
|
|
170
|
-
update,
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function useToast() {
|
|
175
|
-
const [state, setState] = React.useState<State>(memoryState)
|
|
176
|
-
|
|
177
|
-
React.useEffect(() => {
|
|
178
|
-
listeners.push(setState)
|
|
179
|
-
return () => {
|
|
180
|
-
const index = listeners.indexOf(setState)
|
|
181
|
-
if (index > -1) {
|
|
182
|
-
listeners.splice(index, 1)
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}, [state])
|
|
186
|
-
|
|
187
|
-
return {
|
|
188
|
-
...state,
|
|
189
|
-
toast,
|
|
190
|
-
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
export { useToast, toast }
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { RefObject, useEffect } from 'react';
|
|
2
|
-
|
|
3
|
-
function useClickOutside<T extends HTMLElement>(
|
|
4
|
-
ref: RefObject<T | null>,
|
|
5
|
-
handler: (event: MouseEvent | TouchEvent) => void
|
|
6
|
-
): void {
|
|
7
|
-
useEffect(() => {
|
|
8
|
-
const handleClickOutside = (event: MouseEvent | TouchEvent) => {
|
|
9
|
-
if (!ref || !ref.current || ref.current.contains(event.target as Node)) {
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
handler(event);
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
document.addEventListener('mousedown', handleClickOutside);
|
|
17
|
-
document.addEventListener('touchstart', handleClickOutside);
|
|
18
|
-
|
|
19
|
-
return () => {
|
|
20
|
-
document.removeEventListener('mousedown', handleClickOutside);
|
|
21
|
-
document.removeEventListener('touchstart', handleClickOutside);
|
|
22
|
-
};
|
|
23
|
-
}, [ref, handler]);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export default useClickOutside;
|
package/src/lib/.gitkeep
DELETED
|
File without changes
|
package/src/lib/api.ts
DELETED
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Frontend API layer for beads-kanban-ui webapp
|
|
3
|
-
* Replaces Tauri invoke() calls with HTTP fetch to backend
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { Project, Tag, Bead } from '@/types';
|
|
7
|
-
|
|
8
|
-
const API_BASE = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:3008';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Input for creating a new project
|
|
12
|
-
*/
|
|
13
|
-
export interface CreateProjectInput {
|
|
14
|
-
name: string;
|
|
15
|
-
path: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Input for creating a new tag
|
|
20
|
-
*/
|
|
21
|
-
export interface CreateTagInput {
|
|
22
|
-
name: string;
|
|
23
|
-
color: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* File system entry from directory listing
|
|
28
|
-
*/
|
|
29
|
-
export interface FsEntry {
|
|
30
|
-
name: string;
|
|
31
|
-
path: string;
|
|
32
|
-
isDirectory: boolean;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Git branch status information
|
|
37
|
-
*/
|
|
38
|
-
export interface BranchStatus {
|
|
39
|
-
exists: boolean;
|
|
40
|
-
ahead: number;
|
|
41
|
-
behind: number;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* BD CLI command result
|
|
46
|
-
*/
|
|
47
|
-
export interface BdCommandResult {
|
|
48
|
-
stdout: string;
|
|
49
|
-
stderr: string;
|
|
50
|
-
code: number;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* File watcher event
|
|
55
|
-
*/
|
|
56
|
-
export interface WatchEvent {
|
|
57
|
-
path: string;
|
|
58
|
-
type: string;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Helper for fetch with error handling
|
|
63
|
-
*/
|
|
64
|
-
async function fetchApi<T>(path: string, options?: RequestInit): Promise<T> {
|
|
65
|
-
const res = await fetch(`${API_BASE}${path}`, {
|
|
66
|
-
...options,
|
|
67
|
-
headers: {
|
|
68
|
-
'Content-Type': 'application/json',
|
|
69
|
-
...options?.headers,
|
|
70
|
-
},
|
|
71
|
-
});
|
|
72
|
-
if (!res.ok) {
|
|
73
|
-
throw new Error(`API error: ${res.status} ${res.statusText}`);
|
|
74
|
-
}
|
|
75
|
-
return res.json();
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Projects API
|
|
80
|
-
*/
|
|
81
|
-
export const projects = {
|
|
82
|
-
list: () => fetchApi<Project[]>('/api/projects'),
|
|
83
|
-
|
|
84
|
-
create: (data: CreateProjectInput) => fetchApi<Project>('/api/projects', {
|
|
85
|
-
method: 'POST',
|
|
86
|
-
body: JSON.stringify(data),
|
|
87
|
-
}),
|
|
88
|
-
|
|
89
|
-
update: (id: string, data: Partial<Project>) => fetchApi<Project>(`/api/projects/${id}`, {
|
|
90
|
-
method: 'PATCH',
|
|
91
|
-
body: JSON.stringify(data),
|
|
92
|
-
}),
|
|
93
|
-
|
|
94
|
-
delete: (id: string) => fetchApi<void>(`/api/projects/${id}`, { method: 'DELETE' }),
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Tags API
|
|
99
|
-
*/
|
|
100
|
-
export const tags = {
|
|
101
|
-
list: () => fetchApi<Tag[]>('/api/tags'),
|
|
102
|
-
|
|
103
|
-
create: (data: CreateTagInput) => fetchApi<Tag>('/api/tags', {
|
|
104
|
-
method: 'POST',
|
|
105
|
-
body: JSON.stringify(data),
|
|
106
|
-
}),
|
|
107
|
-
|
|
108
|
-
delete: (id: string) => fetchApi<void>(`/api/tags/${id}`, { method: 'DELETE' }),
|
|
109
|
-
|
|
110
|
-
addToProject: (projectId: string, tagId: string) => fetchApi<void>('/api/project-tags', {
|
|
111
|
-
method: 'POST',
|
|
112
|
-
body: JSON.stringify({ projectId, tagId }),
|
|
113
|
-
}),
|
|
114
|
-
|
|
115
|
-
removeFromProject: (projectId: string, tagId: string) => fetchApi<void>(
|
|
116
|
-
`/api/project-tags/${projectId}/${tagId}`,
|
|
117
|
-
{ method: 'DELETE' }
|
|
118
|
-
),
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Beads API
|
|
123
|
-
*/
|
|
124
|
-
export const beads = {
|
|
125
|
-
read: (path: string) => fetchApi<{ beads: Bead[] }>(
|
|
126
|
-
`/api/beads?path=${encodeURIComponent(path)}`
|
|
127
|
-
),
|
|
128
|
-
|
|
129
|
-
addComment: (path: string, beadId: string, text: string, author: string) =>
|
|
130
|
-
fetchApi<Bead>('/api/beads/comment', {
|
|
131
|
-
method: 'POST',
|
|
132
|
-
body: JSON.stringify({ path, bead_id: beadId, text, author }),
|
|
133
|
-
}),
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* BD CLI API
|
|
138
|
-
*/
|
|
139
|
-
export const bd = {
|
|
140
|
-
command: (args: string[], cwd?: string) => fetchApi<BdCommandResult>('/api/bd/command', {
|
|
141
|
-
method: 'POST',
|
|
142
|
-
body: JSON.stringify({ args, cwd }),
|
|
143
|
-
}),
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Git API
|
|
148
|
-
*/
|
|
149
|
-
export const git = {
|
|
150
|
-
branchStatus: (path: string, branch: string) => fetchApi<BranchStatus>(
|
|
151
|
-
`/api/git/branch-status?path=${encodeURIComponent(path)}&branch=${encodeURIComponent(branch)}`
|
|
152
|
-
),
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* File System API
|
|
157
|
-
*/
|
|
158
|
-
export const fs = {
|
|
159
|
-
list: (path: string) => fetchApi<{ entries: FsEntry[] }>(
|
|
160
|
-
`/api/fs/list?path=${encodeURIComponent(path)}`
|
|
161
|
-
),
|
|
162
|
-
|
|
163
|
-
exists: (path: string) => fetchApi<{ exists: boolean }>(
|
|
164
|
-
`/api/fs/exists?path=${encodeURIComponent(path)}`
|
|
165
|
-
),
|
|
166
|
-
|
|
167
|
-
openExternal: (path: string, target: 'vscode' | 'cursor' | 'finder') =>
|
|
168
|
-
fetchApi<{ success: boolean }>('/api/fs/open-external', {
|
|
169
|
-
method: 'POST',
|
|
170
|
-
body: JSON.stringify({ path, target }),
|
|
171
|
-
}),
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* File Watcher (Server-Sent Events)
|
|
176
|
-
*/
|
|
177
|
-
export const watch = {
|
|
178
|
-
beads: (path: string, onEvent: (event: WatchEvent) => void) => {
|
|
179
|
-
const eventSource = new EventSource(
|
|
180
|
-
`${API_BASE}/api/watch/beads?path=${encodeURIComponent(path)}`
|
|
181
|
-
);
|
|
182
|
-
eventSource.onmessage = (e) => onEvent(JSON.parse(e.data));
|
|
183
|
-
eventSource.onerror = () => eventSource.close();
|
|
184
|
-
return () => eventSource.close();
|
|
185
|
-
},
|
|
186
|
-
};
|