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
|
||||
path: audit.md
|
||||
design:
|
||||
status: pending
|
||||
status: draft
|
||||
path: design.md
|
||||
qa_plan:
|
||||
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