Skip to content

Commit ee16e4c

Browse files
author
Marvin Zhang
committed
refactor: update project ID type from string to number for API consistency and enhance project handling logic
1 parent ca84870 commit ee16e4c

File tree

5 files changed

+127
-59
lines changed

5 files changed

+127
-59
lines changed

packages/mcp/src/api/devlog-api-client.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,27 @@ export class DevlogApiClient {
110110
* Unwrap standardized API response
111111
*/
112112
private unwrapApiResponse<T>(response: any): T {
113-
// Handle standardized API response format
114-
if (response && response.success === true) {
113+
// Handle standardized API response format with success/data wrapper (projects API)
114+
if (
115+
response &&
116+
typeof response === 'object' &&
117+
response.success === true &&
118+
'data' in response
119+
) {
115120
return response.data;
116121
}
117122

118-
// Handle legacy direct response (during transition)
123+
// Handle paginated response format (devlogs list API)
124+
if (
125+
response &&
126+
typeof response === 'object' &&
127+
'items' in response &&
128+
'pagination' in response
129+
) {
130+
return response as T;
131+
}
132+
133+
// Handle direct response (individual devlog, etc.)
119134
return response;
120135
}
121136

packages/mcp/src/tools/index.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,30 @@ export const allTools: Tool[] = [...devlogTools, ...projectTools];
99

1010
// Re-export individual tool groups for specific use cases
1111
export { devlogTools, projectTools };
12+
13+
// Legacy exports for backward compatibility and test compatibility
14+
export const coreTools = devlogTools.filter((tool) =>
15+
['devlog_create', 'devlog_get', 'devlog_update', 'devlog_list', 'devlog_close'].includes(
16+
tool.name,
17+
),
18+
);
19+
20+
export const searchTools = devlogTools.filter((tool) =>
21+
['devlog_search', 'devlog_discover_related'].includes(tool.name),
22+
);
23+
24+
export const progressTools = devlogTools.filter((tool) =>
25+
[
26+
'devlog_add_note',
27+
'devlog_update_with_note',
28+
'devlog_complete',
29+
'devlog_archive',
30+
'devlog_unarchive',
31+
].includes(tool.name),
32+
);
33+
34+
export const aiContextTools: Tool[] = []; // Currently no AI context specific tools
35+
36+
export const chatTools: Tool[] = []; // Currently no chat specific tools
37+
38+
export const workspaceTools = projectTools; // Projects are the new workspaces

packages/web/app/components/layout/NavigationBreadcrumb.tsx

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import { usePathname, useRouter } from 'next/navigation';
55
import { useProject } from '@/contexts/ProjectContext';
66
import Link from 'next/link';
77
import { FolderIcon, BookOpenIcon, CheckIcon, ChevronDownIcon } from 'lucide-react';
8-
import {
9-
Breadcrumb,
10-
BreadcrumbItem,
11-
BreadcrumbLink,
12-
BreadcrumbList,
13-
BreadcrumbSeparator
8+
import {
9+
Breadcrumb,
10+
BreadcrumbItem,
11+
BreadcrumbLink,
12+
BreadcrumbList,
13+
BreadcrumbSeparator,
1414
} from '@/components/ui/breadcrumb';
1515
import {
1616
DropdownMenu,
@@ -37,8 +37,16 @@ export function NavigationBreadcrumb() {
3737

3838
const getProjectColor = (name: string) => {
3939
const colors = [
40-
'#1890ff', '#52c41a', '#faad14', '#f5222d', '#722ed1',
41-
'#13c2c2', '#eb2f96', '#fa8c16', '#a0d911', '#2f54eb',
40+
'#1890ff',
41+
'#52c41a',
42+
'#faad14',
43+
'#f5222d',
44+
'#722ed1',
45+
'#13c2c2',
46+
'#eb2f96',
47+
'#fa8c16',
48+
'#a0d911',
49+
'#2f54eb',
4250
];
4351

4452
let hash = 0;
@@ -49,20 +57,20 @@ export function NavigationBreadcrumb() {
4957
return colors[Math.abs(hash) % colors.length];
5058
};
5159

52-
const switchProject = async (projectId: string) => {
60+
const switchProject = async (projectId: number) => {
5361
if (switchingProject || currentProject?.projectId === projectId) return;
5462

5563
try {
5664
setSwitchingProject(true);
57-
65+
5866
const targetProject = projects.find((p) => p.id === projectId);
5967
if (!targetProject) {
6068
throw new Error('Project not found');
6169
}
6270

6371
// Save project to localStorage for persistence
6472
if (typeof window !== 'undefined') {
65-
localStorage.setItem('devlog-current-project', projectId);
73+
localStorage.setItem('devlog-current-project', projectId.toString());
6674
}
6775

6876
// Update the current project
@@ -73,7 +81,7 @@ export function NavigationBreadcrumb() {
7381
});
7482

7583
toast.success(`Switched to project: ${targetProject.name}`);
76-
84+
7785
// Navigate to the project dashboard
7886
router.push(`/projects/${projectId}`);
7987
} catch (error) {
@@ -104,7 +112,7 @@ export function NavigationBreadcrumb() {
104112
<DropdownMenuContent align="start" className="w-64">
105113
{projects.map((project) => {
106114
const isCurrentProject = currentProject.projectId === project.id;
107-
115+
108116
return (
109117
<DropdownMenuItem
110118
key={project.id}
@@ -119,16 +127,10 @@ export function NavigationBreadcrumb() {
119127
{getProjectInitials(project.name)}
120128
</div>
121129
<div className="flex-1 min-w-0">
122-
<div className="text-sm font-medium truncate">
123-
{project.name}
124-
</div>
125-
<div className="text-xs text-muted-foreground truncate">
126-
{project.id}
127-
</div>
130+
<div className="text-sm font-medium truncate">{project.name}</div>
131+
<div className="text-xs text-muted-foreground truncate">{project.id}</div>
128132
</div>
129-
{isCurrentProject && (
130-
<CheckIcon size={14} className="text-primary flex-shrink-0" />
131-
)}
133+
{isCurrentProject && <CheckIcon size={14} className="text-primary flex-shrink-0" />}
132134
</DropdownMenuItem>
133135
);
134136
})}
@@ -143,22 +145,22 @@ export function NavigationBreadcrumb() {
143145
// Handle hierarchical project-based routes
144146
if (pathname.startsWith('/projects/')) {
145147
const pathParts = pathname.split('/').filter(Boolean);
146-
148+
147149
if (pathParts.length >= 2) {
148150
// Add Projects breadcrumb (always linkable)
149151
items.push({
150152
href: '/projects',
151153
label: 'Projects',
152154
icon: <FolderIcon size={14} />,
153155
});
154-
156+
155157
// Add project selector dropdown (instead of simple project name)
156158
if (currentProject?.project) {
157159
items.push({
158160
component: renderProjectDropdown(),
159161
});
160162
}
161-
163+
162164
// Remove all sub-path items (devlogs, create, etc.) - these are now handled by sidebar
163165
// The sidebar will show contextual navigation items based on the current route
164166
}

packages/web/app/contexts/ProjectContext.tsx

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
44

55
export interface ProjectMetadata {
6-
id: string;
6+
id: number; // Changed from string to number to match API
77
name: string;
88
description?: string;
99
tags?: string[];
@@ -12,7 +12,7 @@ export interface ProjectMetadata {
1212
}
1313

1414
export interface ProjectContext {
15-
projectId: string;
15+
projectId: number; // Changed from string to number to match API
1616
project: ProjectMetadata;
1717
isDefault: boolean;
1818
}
@@ -50,16 +50,17 @@ export function ProjectProvider({ children }: ProjectProviderProps) {
5050
}
5151

5252
const data = await response.json();
53-
setProjects(data.projects || []);
53+
// Handle both old and new response formats for backward compatibility
54+
const projectsList = data.success ? data.data : data.projects || data || [];
55+
setProjects(projectsList);
5456

55-
// If no current project is set, set the default project
56-
if (!currentProject && data.projects?.length > 0) {
57-
const defaultProject =
58-
data.projects.find((p: ProjectMetadata) => p.id === 'default') || data.projects[0];
57+
// If no current project is set, set the first project as default
58+
if (!currentProject && projectsList?.length > 0) {
59+
const firstProject = projectsList[0];
5960
setCurrentProject({
60-
projectId: defaultProject.id,
61-
project: defaultProject,
62-
isDefault: defaultProject.id === 'default',
61+
projectId: firstProject.id,
62+
project: firstProject,
63+
isDefault: firstProject.id === 1, // Assume project with ID 1 is the default
6364
});
6465
}
6566
} catch (err) {
@@ -81,12 +82,12 @@ export function ProjectProvider({ children }: ProjectProviderProps) {
8182
if (typeof window !== 'undefined') {
8283
const savedProjectId = localStorage.getItem('devlog-current-project');
8384
if (savedProjectId && projects.length > 0) {
84-
const savedProject = projects.find((p) => p.id === savedProjectId);
85+
const savedProject = projects.find((p) => p.id === parseInt(savedProjectId, 10));
8586
if (savedProject) {
8687
setCurrentProject({
8788
projectId: savedProject.id,
8889
project: savedProject,
89-
isDefault: savedProject.id === 'default',
90+
isDefault: savedProject.id === 1, // Assume project with ID 1 is the default
9091
});
9192
}
9293
}
@@ -96,7 +97,7 @@ export function ProjectProvider({ children }: ProjectProviderProps) {
9697
// Save current project to localStorage
9798
useEffect(() => {
9899
if (typeof window !== 'undefined' && currentProject) {
99-
localStorage.setItem('devlog-current-project', currentProject.projectId);
100+
localStorage.setItem('devlog-current-project', currentProject.projectId.toString());
100101
}
101102
}, [currentProject]);
102103

packages/web/app/projects/ProjectManagementPage.tsx

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,23 @@ import { useProject } from '@/contexts/ProjectContext';
55
import { useRouter } from 'next/navigation';
66
import { PageLayout } from '@/components/layout/PageLayout';
77
import { Button } from '@/components/ui/button';
8-
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
8+
import {
9+
Card,
10+
CardContent,
11+
CardDescription,
12+
CardFooter,
13+
CardHeader,
14+
CardTitle,
15+
} from '@/components/ui/card';
916
import { Badge } from '@/components/ui/badge';
1017
import { Alert, AlertDescription } from '@/components/ui/alert';
11-
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
18+
import {
19+
Dialog,
20+
DialogContent,
21+
DialogFooter,
22+
DialogHeader,
23+
DialogTitle,
24+
} from '@/components/ui/dialog';
1225
import { Input } from '@/components/ui/input';
1326
import { Label } from '@/components/ui/label';
1427
import { Textarea } from '@/components/ui/textarea';
@@ -37,7 +50,7 @@ export function ProjectManagementPage() {
3750

3851
const handleCreateProject = async (e: React.FormEvent) => {
3952
e.preventDefault();
40-
53+
4154
if (!formData.name.trim()) {
4255
toast.error('Project name is required');
4356
return;
@@ -72,18 +85,18 @@ export function ProjectManagementPage() {
7285
}
7386
};
7487

75-
const handleViewProject = (projectId: string) => {
88+
const handleViewProject = (projectId: number) => {
7689
router.push(`/projects/${projectId}`);
7790
};
7891

79-
const getProjectStatusColor = (projectId: string) => {
80-
if (projectId === 'default') return 'blue';
92+
const getProjectStatusColor = (projectId: number) => {
93+
if (projectId === 1) return 'blue'; // Default project
8194
if (currentProject?.projectId === projectId) return 'green';
8295
return 'default';
8396
};
8497

85-
const getProjectStatusText = (projectId: string) => {
86-
if (projectId === 'default') return 'Default';
98+
const getProjectStatusText = (projectId: number) => {
99+
if (projectId === 1) return 'Default'; // Default project
87100
if (currentProject?.projectId === projectId) return 'Active';
88101
return 'Available';
89102
};
@@ -116,7 +129,7 @@ export function ProjectManagementPage() {
116129
return (
117130
<PageLayout
118131
actions={
119-
<Button
132+
<Button
120133
size="lg"
121134
onClick={() => setIsModalVisible(true)}
122135
className="flex items-center gap-2"
@@ -138,16 +151,16 @@ export function ProjectManagementPage() {
138151
</p>
139152
</div>
140153
</div>
141-
154+
142155
<div className="px-6 py-8">
143156
<div className="max-w-7xl mx-auto">
144157
<div className="grid gap-6 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 mb-8">
145158
{projects.map((project) => (
146159
<Card
147160
key={project.id}
148161
className={`cursor-pointer transition-all hover:shadow-lg ${
149-
currentProject?.projectId === project.id
150-
? 'border-primary shadow-primary/20'
162+
currentProject?.projectId === project.id
163+
? 'border-primary shadow-primary/20'
151164
: ''
152165
}`}
153166
onClick={() => handleViewProject(project.id)}
@@ -159,7 +172,11 @@ export function ProjectManagementPage() {
159172
<CardTitle className="text-lg">{project.name}</CardTitle>
160173
</div>
161174
<div className="flex items-center gap-2">
162-
<Badge variant={getProjectStatusColor(project.id) === 'green' ? 'default' : 'secondary'}>
175+
<Badge
176+
variant={
177+
getProjectStatusColor(project.id) === 'green' ? 'default' : 'secondary'
178+
}
179+
>
163180
{getProjectStatusText(project.id)}
164181
</Badge>
165182
<Button
@@ -175,16 +192,22 @@ export function ProjectManagementPage() {
175192
</div>
176193
</div>
177194
</CardHeader>
178-
195+
179196
<CardContent className="pb-3">
180197
<CardDescription className="line-clamp-2 mb-4">
181198
{project.description || 'No description provided'}
182199
</CardDescription>
183200

184201
<div className="text-xs text-muted-foreground space-y-1 mb-4">
185-
<div><strong>ID:</strong> {project.id}</div>
186-
<div><strong>Created:</strong> {new Date(project.createdAt).toLocaleDateString()}</div>
187-
<div><strong>Updated:</strong> {new Date(project.updatedAt).toLocaleDateString()}</div>
202+
<div>
203+
<strong>ID:</strong> {project.id}
204+
</div>
205+
<div>
206+
<strong>Created:</strong> {new Date(project.createdAt).toLocaleDateString()}
207+
</div>
208+
<div>
209+
<strong>Updated:</strong> {new Date(project.updatedAt).toLocaleDateString()}
210+
</div>
188211
</div>
189212

190213
{project.tags && project.tags.length > 0 && (
@@ -238,7 +261,7 @@ export function ProjectManagementPage() {
238261
<p className="text-muted-foreground mb-8 text-lg max-w-md mx-auto">
239262
Create your first project to get started with organizing your development work.
240263
</p>
241-
<Button
264+
<Button
242265
size="lg"
243266
onClick={() => setIsModalVisible(true)}
244267
className="flex items-center gap-2 px-8 py-3"

0 commit comments

Comments
 (0)