From fd7949cb18806a212e46185eaf325bbdeaae77ac Mon Sep 17 00:00:00 2001 From: rdev-worker Date: Wed, 11 Feb 2026 02:25:45 +0000 Subject: [PATCH] build: /design-feature task-management-ui --- .sdlc/features/task-management-ui/design.md | 614 ++++++++++++++++++ .../features/task-management-ui/manifest.yaml | 2 +- .sdlc/features/task-management-ui/spec.md | 69 ++ 3 files changed, 684 insertions(+), 1 deletion(-) create mode 100644 .sdlc/features/task-management-ui/design.md create mode 100644 .sdlc/features/task-management-ui/spec.md diff --git a/.sdlc/features/task-management-ui/design.md b/.sdlc/features/task-management-ui/design.md new file mode 100644 index 0000000..d5760b8 --- /dev/null +++ b/.sdlc/features/task-management-ui/design.md @@ -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 { + 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 { + const res = await api.get>('/projects'); + return res.data; +} + +export async function getProject(id: string): Promise { + const res = await api.get>(`/projects/${id}`); + return res.data; +} + +export async function createProject(req: CreateProjectRequest): Promise { + const res = await api.post>('/projects', req); + return res.data; +} + +export async function updateProject(id: string, req: UpdateProjectRequest): Promise { + const res = await api.put>(`/projects/${id}`, req); + return res.data; +} + +export async function deleteProject(id: string): Promise { + 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([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(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(null); + const [tasks, setTasks] = useState([]); + const [labels, setLabels] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(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 +│ └── +│ ├── /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 ( + + } header={...}> + + } /> + } /> + } /> + + + + ); +} +``` + +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 + + Error + {error.message} + +``` + +## 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. diff --git a/.sdlc/features/task-management-ui/manifest.yaml b/.sdlc/features/task-management-ui/manifest.yaml index cc5cb14..0e6848a 100644 --- a/.sdlc/features/task-management-ui/manifest.yaml +++ b/.sdlc/features/task-management-ui/manifest.yaml @@ -13,7 +13,7 @@ artifacts: status: pending path: audit.md design: - status: pending + status: draft path: design.md qa_plan: status: pending diff --git a/.sdlc/features/task-management-ui/spec.md b/.sdlc/features/task-management-ui/spec.md new file mode 100644 index 0000000..9fcec8f --- /dev/null +++ b/.sdlc/features/task-management-ui/spec.md @@ -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