build: /design-feature task-management-ui
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
parent
9e748a7b97
commit
fd7949cb18
614
.sdlc/features/task-management-ui/design.md
Normal file
614
.sdlc/features/task-management-ui/design.md
Normal file
@ -0,0 +1,614 @@
|
|||||||
|
# Design: Task Management UI
|
||||||
|
|
||||||
|
## Architecture Approach
|
||||||
|
|
||||||
|
This feature transforms `studio-ui` from a static placeholder dashboard into a functional task management application. It adds client-side routing, API integration, state management, and CRUD pages that consume the REST APIs defined by the `data-models` feature.
|
||||||
|
|
||||||
|
| Layer | What Changes |
|
||||||
|
|-------|-------------|
|
||||||
|
| **Routing** (`src/App.tsx`) | Replace static content with React Router; add route definitions |
|
||||||
|
| **Pages** (`src/pages/`) | New page components: ProjectList, ProjectDetail |
|
||||||
|
| **Components** (`src/components/`) | New feature components: ProjectCard, TaskBoard, TaskColumn, TaskCard, dialogs |
|
||||||
|
| **Hooks** (`src/hooks/`) | Custom hooks for API data fetching and mutation |
|
||||||
|
| **API** (`src/api/`) | API client instance and typed request functions |
|
||||||
|
| **Types** (`src/types/`) | TypeScript interfaces matching backend response shapes |
|
||||||
|
| **Navigation** (`src/App.tsx`) | Update sidebar items to include Projects route |
|
||||||
|
|
||||||
|
The existing shared packages (`ui`, `layout`, `auth`, `api-client`) are consumed as-is. No shared package changes are required.
|
||||||
|
|
||||||
|
## Data Model (Frontend Types)
|
||||||
|
|
||||||
|
TypeScript interfaces mirroring the backend API response shapes:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/types/project.ts
|
||||||
|
interface Project {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
status: "active" | "archived";
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CreateProjectRequest {
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateProjectRequest {
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
status?: "active" | "archived";
|
||||||
|
}
|
||||||
|
|
||||||
|
// src/types/task.ts
|
||||||
|
type TaskStatus = "todo" | "in_progress" | "done";
|
||||||
|
type TaskPriority = "low" | "medium" | "high";
|
||||||
|
|
||||||
|
interface Task {
|
||||||
|
id: string;
|
||||||
|
project_id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
status: TaskStatus;
|
||||||
|
priority: TaskPriority;
|
||||||
|
position: number;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CreateTaskRequest {
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
priority?: TaskPriority;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateTaskRequest {
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
status?: TaskStatus;
|
||||||
|
priority?: TaskPriority;
|
||||||
|
position?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// src/types/label.ts
|
||||||
|
interface Label {
|
||||||
|
id: string;
|
||||||
|
project_id: string;
|
||||||
|
name: string;
|
||||||
|
color: string;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CreateLabelRequest {
|
||||||
|
name: string;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// src/types/assignment.ts
|
||||||
|
interface Assignment {
|
||||||
|
id: string;
|
||||||
|
task_id: string;
|
||||||
|
assignee: string;
|
||||||
|
created_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CreateAssignmentRequest {
|
||||||
|
assignee: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
All responses from the API are wrapped in the `{data, meta}` envelope:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface APIResponse<T> {
|
||||||
|
data: T;
|
||||||
|
meta?: {
|
||||||
|
request_id: string;
|
||||||
|
timestamp: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Integration Layer
|
||||||
|
|
||||||
|
### Client Setup
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/api/client.ts
|
||||||
|
import { createClient } from '@foundary-test-1770773605/api-client';
|
||||||
|
|
||||||
|
const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:8001';
|
||||||
|
|
||||||
|
export const api = createClient({
|
||||||
|
baseUrl: `${API_BASE}/api/studio-api`,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Functions
|
||||||
|
|
||||||
|
Typed wrappers around the API client, one file per resource:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/api/projects.ts
|
||||||
|
export async function listProjects(): Promise<Project[]> {
|
||||||
|
const res = await api.get<APIResponse<Project[]>>('/projects');
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getProject(id: string): Promise<Project> {
|
||||||
|
const res = await api.get<APIResponse<Project>>(`/projects/${id}`);
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createProject(req: CreateProjectRequest): Promise<Project> {
|
||||||
|
const res = await api.post<APIResponse<Project>>('/projects', req);
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateProject(id: string, req: UpdateProjectRequest): Promise<Project> {
|
||||||
|
const res = await api.put<APIResponse<Project>>(`/projects/${id}`, req);
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteProject(id: string): Promise<void> {
|
||||||
|
await api.delete(`/projects/${id}`);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Similar patterns for `src/api/tasks.ts`, `src/api/labels.ts`, `src/api/assignments.ts`. Task and label create/list endpoints use the nested project path (`/projects/{projectId}/tasks`), while get/update/delete use flat paths (`/tasks/{id}`).
|
||||||
|
|
||||||
|
## State Management
|
||||||
|
|
||||||
|
### Approach: React hooks + local component state
|
||||||
|
|
||||||
|
No global state library (Redux, Zustand) is needed. Each page manages its own data via custom hooks that encapsulate fetch/mutate/loading/error state. This matches the simplicity of the current codebase.
|
||||||
|
|
||||||
|
### Custom Hooks
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/hooks/useProjects.ts
|
||||||
|
function useProjects() {
|
||||||
|
const [projects, setProjects] = useState<Project[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const refresh = useCallback(async () => { ... }, []);
|
||||||
|
|
||||||
|
useEffect(() => { refresh(); }, [refresh]);
|
||||||
|
|
||||||
|
return { projects, loading, error, refresh };
|
||||||
|
}
|
||||||
|
|
||||||
|
// src/hooks/useProject.ts
|
||||||
|
function useProject(id: string) {
|
||||||
|
// Fetches single project + its tasks + labels
|
||||||
|
const [project, setProject] = useState<Project | null>(null);
|
||||||
|
const [tasks, setTasks] = useState<Task[]>([]);
|
||||||
|
const [labels, setLabels] = useState<Label[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const refresh = useCallback(async () => {
|
||||||
|
// Parallel fetch: project, tasks, labels
|
||||||
|
const [proj, taskList, labelList] = await Promise.all([
|
||||||
|
getProject(id),
|
||||||
|
listTasks(id),
|
||||||
|
listLabels(id),
|
||||||
|
]);
|
||||||
|
setProject(proj);
|
||||||
|
setTasks(taskList);
|
||||||
|
setLabels(labelList);
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
return { project, tasks, labels, loading, error, refresh };
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
After a mutation (create/update/delete), the hook's `refresh()` is called to re-fetch from the server. This avoids stale state and keeps the implementation simple.
|
||||||
|
|
||||||
|
## Component Diagram
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── App.tsx # Router + DashboardShell layout
|
||||||
|
├── api/
|
||||||
|
│ ├── client.ts # API client instance
|
||||||
|
│ ├── projects.ts # Project API functions
|
||||||
|
│ ├── tasks.ts # Task API functions
|
||||||
|
│ ├── labels.ts # Label API functions
|
||||||
|
│ └── assignments.ts # Assignment API functions
|
||||||
|
├── types/
|
||||||
|
│ ├── project.ts # Project types
|
||||||
|
│ ├── task.ts # Task types
|
||||||
|
│ ├── label.ts # Label types
|
||||||
|
│ └── assignment.ts # Assignment types
|
||||||
|
├── hooks/
|
||||||
|
│ ├── useProjects.ts # Project list data
|
||||||
|
│ └── useProject.ts # Single project + tasks + labels
|
||||||
|
├── pages/
|
||||||
|
│ ├── ProjectListPage.tsx # /projects route
|
||||||
|
│ └── ProjectDetailPage.tsx # /projects/:id route
|
||||||
|
├── components/
|
||||||
|
│ ├── ProjectCard.tsx # Card for project list
|
||||||
|
│ ├── CreateProjectDialog.tsx # Dialog for creating/editing projects
|
||||||
|
│ ├── DeleteConfirmDialog.tsx # Reusable delete confirmation
|
||||||
|
│ ├── TaskBoard.tsx # Kanban-style board with columns
|
||||||
|
│ ├── TaskColumn.tsx # Single status column
|
||||||
|
│ ├── TaskCard.tsx # Individual task card
|
||||||
|
│ ├── CreateTaskDialog.tsx # Dialog for creating/editing tasks
|
||||||
|
│ ├── LabelList.tsx # Label management section
|
||||||
|
│ ├── CreateLabelDialog.tsx # Dialog for creating labels
|
||||||
|
│ └── AssignmentList.tsx # Assignment display in task detail
|
||||||
|
├── index.css
|
||||||
|
├── main.tsx
|
||||||
|
└── lib/
|
||||||
|
└── logger.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
### Component Interaction Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
App.tsx
|
||||||
|
├── DashboardShell (from @layout)
|
||||||
|
│ ├── Sidebar → nav items including /projects
|
||||||
|
│ ├── Header
|
||||||
|
│ └── <Routes>
|
||||||
|
│ ├── /projects → ProjectListPage
|
||||||
|
│ │ ├── useProjects() hook
|
||||||
|
│ │ ├── ProjectCard[] (grid)
|
||||||
|
│ │ └── CreateProjectDialog
|
||||||
|
│ │
|
||||||
|
│ └── /projects/:id → ProjectDetailPage
|
||||||
|
│ ├── useProject(id) hook
|
||||||
|
│ ├── Project header (name, status, actions)
|
||||||
|
│ ├── TaskBoard
|
||||||
|
│ │ ├── TaskColumn (todo)
|
||||||
|
│ │ │ └── TaskCard[]
|
||||||
|
│ │ ├── TaskColumn (in_progress)
|
||||||
|
│ │ │ └── TaskCard[]
|
||||||
|
│ │ └── TaskColumn (done)
|
||||||
|
│ │ └── TaskCard[]
|
||||||
|
│ ├── CreateTaskDialog
|
||||||
|
│ ├── LabelList
|
||||||
|
│ │ └── CreateLabelDialog
|
||||||
|
│ └── DeleteConfirmDialog
|
||||||
|
```
|
||||||
|
|
||||||
|
## Page Designs
|
||||||
|
|
||||||
|
### Project List Page (`/projects`)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ Header: "Projects" [+ New Project] │
|
||||||
|
├─────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||||
|
│ │ Project A │ │ Project B │ │ Project C │ │
|
||||||
|
│ │ │ │ │ │ │ │
|
||||||
|
│ │ Desc... │ │ Desc... │ │ Desc... │ │
|
||||||
|
│ │ │ │ │ │ │ │
|
||||||
|
│ │ ●Active │ │ ●Active │ │ ○Archived │ │
|
||||||
|
│ │ 5 tasks │ │ 12 tasks │ │ 3 tasks │ │
|
||||||
|
│ └──────────┘ └──────────┘ └──────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────────────────────────────────┐ │
|
||||||
|
│ │ No projects yet? Create your first one! │ │
|
||||||
|
│ │ [+ Create Project] │ │
|
||||||
|
│ └──────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
- Grid layout: `grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4`
|
||||||
|
- Empty state card when no projects exist
|
||||||
|
- Project cards link to `/projects/:id`
|
||||||
|
- Badge shows status (success for active, secondary for archived)
|
||||||
|
|
||||||
|
### Project Detail Page (`/projects/:id`)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ Projects > Project A [Edit] [Del] │
|
||||||
|
├─────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ Labels: [Bug] [Feature] [Docs] [+ Add Label] │
|
||||||
|
│ │
|
||||||
|
│ ┌─────────────┬──────────────┬───────────────┐ │
|
||||||
|
│ │ Todo (3) │ In Progress │ Done (5) │ │
|
||||||
|
│ │ │ (2) │ │ │
|
||||||
|
│ │ ┌─────────┐│ ┌──────────┐ │ ┌───────────┐ │ │
|
||||||
|
│ │ │ Task 1 ││ │ Task 4 │ │ │ Task 6 │ │ │
|
||||||
|
│ │ │ ▲ High ││ │ ● Medium │ │ │ ▼ Low │ │ │
|
||||||
|
│ │ │ @alice ││ │ @bob │ │ │ @alice │ │ │
|
||||||
|
│ │ └─────────┘│ └──────────┘ │ └───────────┘ │ │
|
||||||
|
│ │ ┌─────────┐│ │ ┌───────────┐ │ │
|
||||||
|
│ │ │ Task 2 ││ │ │ Task 7 │ │ │
|
||||||
|
│ │ │ ● Med ││ │ │ ● Medium │ │ │
|
||||||
|
│ │ └─────────┘│ │ └───────────┘ │ │
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ │ [+ Add] │ │ │ │
|
||||||
|
│ └─────────────┴──────────────┴───────────────┘ │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
- Kanban board with three fixed columns: Todo, In Progress, Done
|
||||||
|
- Each column has a header with count badge
|
||||||
|
- Task cards show title, priority badge, assignee(s)
|
||||||
|
- Clicking a task card opens an edit sheet/dialog
|
||||||
|
- "Add Task" button at the bottom of the Todo column (or a floating button)
|
||||||
|
- Labels displayed as a horizontal row of badges above the board
|
||||||
|
|
||||||
|
## Key Component Specifications
|
||||||
|
|
||||||
|
### ProjectCard
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Uses: Card, CardHeader, CardTitle, CardDescription, CardContent, Badge
|
||||||
|
// Props: project: Project, taskCount?: number, onClick: () => void
|
||||||
|
// Behavior: Click navigates to /projects/:id
|
||||||
|
```
|
||||||
|
|
||||||
|
### TaskBoard
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Props: tasks: Task[], onCreateTask, onUpdateTask, onDeleteTask
|
||||||
|
// Groups tasks by status into three TaskColumn components
|
||||||
|
// Horizontal scroll on narrow viewports: flex with overflow-x-auto
|
||||||
|
```
|
||||||
|
|
||||||
|
### TaskCard
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Uses: Card, Badge
|
||||||
|
// Props: task: Task, assignments: Assignment[], onClick: () => void
|
||||||
|
// Shows: title, priority badge (color-coded), assignee avatars/names
|
||||||
|
// Click opens task edit dialog
|
||||||
|
```
|
||||||
|
|
||||||
|
### Priority Badge Colors
|
||||||
|
|
||||||
|
| Priority | Badge Variant | Color |
|
||||||
|
|----------|--------------|-------|
|
||||||
|
| High | `error` | Red |
|
||||||
|
| Medium | `warning` | Yellow/Amber |
|
||||||
|
| Low | `info` | Blue |
|
||||||
|
|
||||||
|
### Status Column Colors
|
||||||
|
|
||||||
|
| Status | Label |
|
||||||
|
|--------|-------|
|
||||||
|
| `todo` | "Todo" |
|
||||||
|
| `in_progress` | "In Progress" |
|
||||||
|
| `done` | "Done" |
|
||||||
|
|
||||||
|
### CreateProjectDialog
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Uses: Dialog, Input, Textarea, Button, Label
|
||||||
|
// Fields: name (required, max 200), description (optional, max 1000)
|
||||||
|
// Mode: create (POST) or edit (PUT) based on whether project prop is passed
|
||||||
|
// On submit: calls API, then refresh() from hook
|
||||||
|
```
|
||||||
|
|
||||||
|
### CreateTaskDialog
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Uses: Dialog, Input, Textarea, Select, Button, Label
|
||||||
|
// Fields: title (required, max 300), description (optional, max 5000),
|
||||||
|
// priority (select: low/medium/high), status (select: todo/in_progress/done)
|
||||||
|
// Mode: create (POST, status defaults to todo) or edit (PUT)
|
||||||
|
```
|
||||||
|
|
||||||
|
### DeleteConfirmDialog
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Uses: Dialog, Button
|
||||||
|
// Props: title, message, onConfirm, onCancel
|
||||||
|
// "Are you sure?" with destructive-variant confirm button
|
||||||
|
// Reusable for projects, tasks, labels
|
||||||
|
```
|
||||||
|
|
||||||
|
## Routing Setup
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// src/App.tsx
|
||||||
|
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<BrowserRouter>
|
||||||
|
<DashboardShell sidebar={<Sidebar items={navItems} />} header={...}>
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" element={<Navigate to="/projects" replace />} />
|
||||||
|
<Route path="/projects" element={<ProjectListPage />} />
|
||||||
|
<Route path="/projects/:id" element={<ProjectDetailPage />} />
|
||||||
|
</Routes>
|
||||||
|
</DashboardShell>
|
||||||
|
</BrowserRouter>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The sidebar `navItems` will be updated:
|
||||||
|
```tsx
|
||||||
|
const navItems: NavItem[] = [
|
||||||
|
{ label: 'Projects', href: '/projects', icon: FolderKanban, active: true },
|
||||||
|
{ label: 'Settings', href: '/settings', icon: Settings },
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
Active state should be derived from current route via `useLocation()`.
|
||||||
|
|
||||||
|
## Error Handling Strategy
|
||||||
|
|
||||||
|
### API Error Handling
|
||||||
|
|
||||||
|
All API functions handle the `{data, meta}` envelope and throw on non-2xx responses:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Errors propagated from api-client
|
||||||
|
try {
|
||||||
|
await createProject(req);
|
||||||
|
} catch (err) {
|
||||||
|
// err.status: HTTP status code
|
||||||
|
// err.message: error message from server
|
||||||
|
// err.body: full error response
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Per-Component Error States
|
||||||
|
|
||||||
|
| Scenario | UI Behavior |
|
||||||
|
|----------|-------------|
|
||||||
|
| Page load failure | Alert banner: "Failed to load projects. Try again." + retry button |
|
||||||
|
| Create/Update validation (400/422) | Field-level error messages in dialog |
|
||||||
|
| Duplicate name conflict (409) | Inline error: "A project with this name already exists" |
|
||||||
|
| Not found (404) | Redirect to project list with toast/alert |
|
||||||
|
| Network error | Alert banner: "Network error. Check your connection." |
|
||||||
|
| Delete failure | Toast/alert: "Failed to delete. Try again." |
|
||||||
|
|
||||||
|
### Error Display Components
|
||||||
|
|
||||||
|
Errors are shown using the existing `Alert` component:
|
||||||
|
```tsx
|
||||||
|
<Alert variant="destructive">
|
||||||
|
<AlertTitle>Error</AlertTitle>
|
||||||
|
<AlertDescription>{error.message}</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
### Input Validation (Client-Side)
|
||||||
|
|
||||||
|
Client-side validation mirrors backend constraints to provide immediate feedback:
|
||||||
|
- Project name: required, 1-200 chars
|
||||||
|
- Project description: max 1000 chars
|
||||||
|
- Task title: required, 1-300 chars
|
||||||
|
- Task description: max 5000 chars
|
||||||
|
- Label name: required, 1-50 chars
|
||||||
|
- Label color: hex format validation (#RRGGBB)
|
||||||
|
- Assignee: required, 1-200 chars
|
||||||
|
|
||||||
|
Client-side validation is **defense in depth** — the backend always re-validates.
|
||||||
|
|
||||||
|
### XSS Prevention
|
||||||
|
|
||||||
|
- React's JSX auto-escapes rendered content — no `dangerouslySetInnerHTML` used
|
||||||
|
- User-provided strings (names, descriptions) rendered as text nodes
|
||||||
|
- Label colors rendered only as CSS `background-color` values, validated as hex
|
||||||
|
|
||||||
|
### Authentication
|
||||||
|
|
||||||
|
- Auth is out of scope per spec — no login UI or token management in this feature
|
||||||
|
- The API client supports bearer token injection when auth is later enabled
|
||||||
|
- Write operations (create/update/delete) work without auth in development (matching backend's `AUTH_ENABLED=false` default)
|
||||||
|
|
||||||
|
### CORS
|
||||||
|
|
||||||
|
- Dev mode: Vite proxy or CORS headers configured for `localhost:3001` → `localhost:8001`
|
||||||
|
- Production: Same-origin or configured CORS in the backend
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
### Data Fetching
|
||||||
|
|
||||||
|
- **Project list:** Single API call (`GET /projects`). No pagination needed for initial scale.
|
||||||
|
- **Project detail:** Three parallel API calls (`GET /projects/:id`, `GET /projects/:id/tasks`, `GET /projects/:id/labels`). Uses `Promise.all` for concurrent loading.
|
||||||
|
- **Assignments:** Fetched lazily when a task card is expanded/clicked, not on page load. This avoids N+1 API calls.
|
||||||
|
|
||||||
|
### Rendering
|
||||||
|
|
||||||
|
- Task board: Three columns rendered as simple lists. No virtualization needed at current scale (< 100 tasks per project).
|
||||||
|
- React key optimization: All lists keyed by entity `id` (UUID).
|
||||||
|
- Dialog components: Rendered conditionally (not mounted until triggered).
|
||||||
|
|
||||||
|
### Bundle Size
|
||||||
|
|
||||||
|
- No additional large dependencies. Uses existing React, React Router, and shared packages.
|
||||||
|
- lucide-react icons are tree-shaken per import.
|
||||||
|
- No chart libraries or heavy visualization for this feature.
|
||||||
|
|
||||||
|
### Caching
|
||||||
|
|
||||||
|
- No client-side cache layer in v1. Each page navigation re-fetches.
|
||||||
|
- Future optimization: add SWR or React Query for caching, deduplication, and background refetching.
|
||||||
|
|
||||||
|
## Migration / Rollout Plan
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
1. **Backend:** The `data-models` feature must be implemented and deployed first. The API endpoints (`/projects`, `/tasks`, etc.) must be available.
|
||||||
|
2. **Database:** PostgreSQL with migrations applied (handled by `data-models` feature).
|
||||||
|
|
||||||
|
### Rollout Steps
|
||||||
|
|
||||||
|
**Step 1: Add routing and page structure**
|
||||||
|
- Install `react-router-dom` (if not already present)
|
||||||
|
- Replace static `App.tsx` content with router setup
|
||||||
|
- Create empty page components
|
||||||
|
|
||||||
|
**Step 2: Add API integration layer**
|
||||||
|
- Create `src/api/client.ts` with base configuration
|
||||||
|
- Create typed API function files per resource
|
||||||
|
- Create TypeScript type definitions
|
||||||
|
|
||||||
|
**Step 3: Build project list page**
|
||||||
|
- `ProjectListPage` with `useProjects` hook
|
||||||
|
- `ProjectCard` component
|
||||||
|
- `CreateProjectDialog` (create + edit modes)
|
||||||
|
- `DeleteConfirmDialog` (reusable)
|
||||||
|
|
||||||
|
**Step 4: Build project detail page**
|
||||||
|
- `ProjectDetailPage` with `useProject` hook
|
||||||
|
- `TaskBoard` + `TaskColumn` + `TaskCard`
|
||||||
|
- `CreateTaskDialog` (create + edit modes)
|
||||||
|
- `LabelList` + `CreateLabelDialog`
|
||||||
|
- `AssignmentList` within task edit
|
||||||
|
|
||||||
|
**Step 5: Polish and integration**
|
||||||
|
- Loading states, error states, empty states
|
||||||
|
- Navigation active states from route
|
||||||
|
- Environment variable for API URL
|
||||||
|
|
||||||
|
### Backward Compatibility
|
||||||
|
|
||||||
|
- The static dashboard content is **replaced**, not preserved alongside
|
||||||
|
- No existing user data to migrate — this is a new feature
|
||||||
|
- The sidebar navigation changes (removes placeholder items, adds Projects)
|
||||||
|
- No API contract changes — this is a pure frontend addition
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
| Variable | Default | Purpose |
|
||||||
|
|----------|---------|---------|
|
||||||
|
| `VITE_API_URL` | `http://localhost:8001` | Backend API base URL |
|
||||||
|
|
||||||
|
### Testing Strategy
|
||||||
|
|
||||||
|
- **Manual testing:** All CRUD operations through the UI against the running backend
|
||||||
|
- **Component tests:** Optional unit tests for complex components (TaskBoard grouping logic)
|
||||||
|
- **E2E tests:** Out of scope for v1; can be added later with Playwright/Cypress
|
||||||
|
|
||||||
|
## Design Decisions
|
||||||
|
|
||||||
|
1. **React hooks over global state:** The app is simple enough that local state per page is sufficient. No Redux/Zustand complexity. Custom hooks encapsulate fetch logic and make it reusable.
|
||||||
|
|
||||||
|
2. **Kanban board over table view:** Task management benefits from visual status columns. The three-column board (Todo, In Progress, Done) provides immediate status overview. Table view can be added as an alternative later.
|
||||||
|
|
||||||
|
3. **Dialogs over separate pages for CRUD:** Create/edit operations use modal dialogs rather than navigating to separate form pages. This keeps the user in context and matches the design system's Dialog component.
|
||||||
|
|
||||||
|
4. **Lazy assignment loading:** Assignments are fetched per-task when clicked, not upfront. This avoids N+1 queries on page load and keeps initial render fast.
|
||||||
|
|
||||||
|
5. **No drag-and-drop in v1:** The backend has a `position` field, but implementing drag-and-drop adds significant complexity (library dependency, position recalculation, optimistic updates). Deferred to a follow-up feature.
|
||||||
|
|
||||||
|
6. **Shared DeleteConfirmDialog:** One reusable confirmation dialog rather than per-entity delete dialogs. Reduces code duplication.
|
||||||
|
|
||||||
|
7. **Environment-based API URL:** Uses `VITE_API_URL` for the backend base URL. In production, this can point to the deployed API. In development, it defaults to `localhost:8001`.
|
||||||
|
|
||||||
|
8. **No client-side routing library beyond React Router:** The sidebar's `NavItem` pattern already supports `href` strings. Active state detection uses `useLocation()` from React Router.
|
||||||
@ -13,7 +13,7 @@ artifacts:
|
|||||||
status: pending
|
status: pending
|
||||||
path: audit.md
|
path: audit.md
|
||||||
design:
|
design:
|
||||||
status: pending
|
status: draft
|
||||||
path: design.md
|
path: design.md
|
||||||
qa_plan:
|
qa_plan:
|
||||||
status: pending
|
status: pending
|
||||||
|
|||||||
69
.sdlc/features/task-management-ui/spec.md
Normal file
69
.sdlc/features/task-management-ui/spec.md
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# Feature: Task Management UI
|
||||||
|
|
||||||
|
## Problem Statement
|
||||||
|
|
||||||
|
The studio application has a static placeholder dashboard with no real functionality. The `data-models` feature provides backend REST APIs for Projects, Tasks, Labels, and Assignments, but there is no frontend to interact with them. Users need a task management interface in `studio-ui` to create and manage projects, organize tasks with statuses and priorities, and assign work — all through an intuitive board/list UI built on the shared design system.
|
||||||
|
|
||||||
|
## User Stories
|
||||||
|
|
||||||
|
- As a **user**, I want to see a list of my projects so I can navigate between them.
|
||||||
|
- As a **user**, I want to create, edit, and delete projects so I can organize my work.
|
||||||
|
- As a **user**, I want to see tasks within a project organized by status (todo, in_progress, done) so I can track progress.
|
||||||
|
- As a **user**, I want to create, edit, and delete tasks within a project so I can manage work items.
|
||||||
|
- As a **user**, I want to set task priority (low, medium, high) so I can focus on what matters.
|
||||||
|
- As a **user**, I want to assign and unassign people to tasks so I can distribute work.
|
||||||
|
- As a **user**, I want to create and manage labels within a project so I can categorize work.
|
||||||
|
- As a **user**, I want the UI to use the shared design system (dark theme, CSS variables) so it looks consistent.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
### Navigation & Routing
|
||||||
|
- [ ] React Router routes: `/projects`, `/projects/:id`, `/projects/:id/settings`
|
||||||
|
- [ ] Sidebar navigation includes "Projects" item with icon
|
||||||
|
- [ ] Breadcrumb navigation within project views
|
||||||
|
|
||||||
|
### Project List Page (`/projects`)
|
||||||
|
- [ ] Displays all projects as cards in a grid layout
|
||||||
|
- [ ] Each card shows name, description, status badge, task count
|
||||||
|
- [ ] "New Project" button opens a create dialog
|
||||||
|
- [ ] Project cards link to project detail view
|
||||||
|
|
||||||
|
### Project Detail Page (`/projects/:id`)
|
||||||
|
- [ ] Header with project name, status, and action buttons (edit, delete)
|
||||||
|
- [ ] Task board view with columns: Todo, In Progress, Done
|
||||||
|
- [ ] Each task card shows title, priority badge, assignees
|
||||||
|
- [ ] "Add Task" button opens create task dialog
|
||||||
|
- [ ] Labels section showing project labels
|
||||||
|
|
||||||
|
### CRUD Dialogs
|
||||||
|
- [ ] Create/Edit Project dialog: name (required), description (optional)
|
||||||
|
- [ ] Create/Edit Task dialog: title (required), description, priority selector, status selector
|
||||||
|
- [ ] Create Label dialog: name (required), color picker
|
||||||
|
- [ ] Delete confirmation dialogs for destructive actions
|
||||||
|
|
||||||
|
### API Integration
|
||||||
|
- [ ] Uses `@foundary-test-1770773605/api-client` for all API calls
|
||||||
|
- [ ] Proper loading states during API calls
|
||||||
|
- [ ] Error handling with user-friendly messages
|
||||||
|
- [ ] Optimistic UI updates where appropriate
|
||||||
|
|
||||||
|
### Responsive Design
|
||||||
|
- [ ] Works on desktop (1024px+) and tablet (768px+)
|
||||||
|
- [ ] Task board scrolls horizontally on smaller screens
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- `data-models` feature (backend APIs for Projects, Tasks, Labels, Assignments)
|
||||||
|
- `@foundary-test-1770773605/ui` (shared component library)
|
||||||
|
- `@foundary-test-1770773605/layout` (dashboard shell)
|
||||||
|
- `@foundary-test-1770773605/api-client` (typed HTTP client)
|
||||||
|
- `react-router-dom` (client-side routing)
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
- Drag-and-drop task reordering (future feature)
|
||||||
|
- Real-time updates via WebSockets
|
||||||
|
- Pagination or infinite scroll
|
||||||
|
- Advanced filtering/search within tasks
|
||||||
|
- Task-label association UI (backend not implemented yet)
|
||||||
|
- Authentication/login UI
|
||||||
Loading…
Reference in New Issue
Block a user