# Design Document: Custom Task Status Management
## Overview
This design document outlines the implementation of a custom task status management system that allows project managers, coordinators, and administrators to define project-specific task statuses with custom names, colors, and ordering. The system will maintain backward compatibility with existing hardcoded statuses while providing flexibility for different production workflows.
The implementation follows the existing pattern established by the custom task type management system, adapting it for status management with additional features for color customization, ordering, and default status designation.
## Architecture
### High-Level Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ Frontend (Vue 3) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ ProjectSettingsView (Tasks Tab) │ │
│ │ └── CustomTaskStatusManager Component │ │
│ │ ├── Status List Display │ │
│ │ ├── Add/Edit Status Dialog │ │
│ │ ├── Delete Confirmation Dialog │ │
│ │ └── Drag-and-Drop Reordering │ │
│ └──────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Status Display Components (Throughout App) │ │
│ │ ├── TaskStatusBadge (with custom colors) │ │
│ │ ├── EditableTaskStatus (dropdowns) │ │
│ │ └── TaskStatusFilter (filter controls) │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
│ HTTP/REST API
▼
┌─────────────────────────────────────────────────────────────┐
│ Backend (FastAPI) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ /api/projects/{id}/custom-task-statuses │ │
│ │ ├── GET - List all statuses │ │
│ │ ├── POST - Create new status │ │
│ │ ├── PUT - Update status │ │
│ │ ├── DELETE - Delete status (with validation) │ │
│ │ └── PATCH - Reorder statuses │ │
│ └──────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Database Models │ │
│ │ ├── Project (custom_task_statuses JSON field) │ │
│ │ └── Task (status field - string reference) │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌───────────────┐
│ SQLite DB │
└───────────────┘
```
### Data Flow
1. **Status Creation**: User creates status → Frontend validates → API creates status → Database updated → UI refreshed
2. **Status Display**: Task loaded → Status resolved (system or custom) → Color applied → Badge rendered
3. **Status Update**: User changes task status → API validates → Task updated → Activity logged → UI updated
4. **Status Deletion**: User deletes status → API checks usage → If in use, require reassignment → Delete → Update tasks
## Components and Interfaces
### Backend Components
#### 1. Database Schema Changes
**Project Model Extension** (`backend/models/project.py`):
```python
class Project(Base):
__tablename__ = "projects"
# ... existing fields ...
# New field for custom task statuses
custom_task_statuses = Column(JSON, nullable=True)
# Structure: [
# {
# "id": "custom_status_1",
# "name": "In Review",
# "color": "#FFA500",
# "order": 0,
# "is_default": false
# },
# ...
# ]
```
**Task Model** (`backend/models/task.py`):
```python
class Task(Base):
__tablename__ = "tasks"
# ... existing fields ...
# Change status from Enum to String to support custom statuses
status = Column(String, nullable=False, default="not_started")
# Will store either system status keys or custom status IDs
```
#### 2. Pydantic Schemas
**Custom Task Status Schemas** (`backend/schemas/custom_task_status.py`):
```python
from pydantic import BaseModel, Field, validator
from typing import List, Optional
import re
class CustomTaskStatus(BaseModel):
"""Schema for a custom task status"""
id: str = Field(..., description="Unique identifier for the status")
name: str = Field(..., min_length=1, max_length=50, description="Display name")
color: str = Field(..., pattern=r'^#[0-9A-Fa-f]{6}$', description="Hex color code")
order: int = Field(..., ge=0, description="Display order")
is_default: bool = Field(default=False, description="Whether this is the default status")
class CustomTaskStatusCreate(BaseModel):
"""Schema for creating a new custom task status"""
name: str = Field(..., min_length=1, max_length=50)
color: Optional[str] = Field(None, pattern=r'^#[0-9A-Fa-f]{6}$')
@validator('name')
def validate_name(cls, v):
# Trim whitespace
v = v.strip()
if not v:
raise ValueError('Status name cannot be empty')
return v
class CustomTaskStatusUpdate(BaseModel):
"""Schema for updating a custom task status"""
name: Optional[str] = Field(None, min_length=1, max_length=50)
color: Optional[str] = Field(None, pattern=r'^#[0-9A-Fa-f]{6}$')
is_default: Optional[bool] = None
class CustomTaskStatusReorder(BaseModel):
"""Schema for reordering statuses"""
status_ids: List[str] = Field(..., description="Ordered list of status IDs")
class CustomTaskStatusDelete(BaseModel):
"""Schema for deleting a status with reassignment"""
reassign_to_status_id: Optional[str] = Field(None, description="Status ID to reassign tasks to")
class AllTaskStatusesResponse(BaseModel):
"""Schema for response containing all task statuses"""
statuses: List[CustomTaskStatus]
system_statuses: List[dict] # [{id: "not_started", name: "Not Started", color: "#gray"}]
default_status_id: str
class TaskStatusInUseError(BaseModel):
"""Schema for error when trying to delete a status in use"""
error: str
status_id: str
task_count: int
task_ids: List[int]
```
#### 3. API Endpoints
**Router** (`backend/routers/projects.py`):
```python
# System statuses (read-only, for backward compatibility)
SYSTEM_TASK_STATUSES = [
{"id": "not_started", "name": "Not Started", "color": "#6B7280"},
{"id": "in_progress", "name": "In Progress", "color": "#3B82F6"},
{"id": "submitted", "name": "Submitted", "color": "#F59E0B"},
{"id": "approved", "name": "Approved", "color": "#10B981"},
{"id": "retake", "name": "Retake", "color": "#EF4444"}
]
DEFAULT_STATUS_COLORS = [
"#3B82F6", "#10B981", "#F59E0B", "#EF4444", "#8B5CF6",
"#EC4899", "#14B8A6", "#F97316", "#06B6D4", "#84CC16"
]
@router.get("/{project_id}/custom-task-statuses")
async def get_all_task_statuses(
project_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user_with_db)
):
"""Get all task statuses (system + custom) for a project"""
# Implementation details...
@router.post("/{project_id}/custom-task-statuses", status_code=201)
async def create_custom_task_status(
project_id: int,
status_data: CustomTaskStatusCreate,
db: Session = Depends(get_db),
current_user: User = Depends(require_coordinator_or_admin)
):
"""Create a new custom task status"""
# Implementation details...
@router.put("/{project_id}/custom-task-statuses/{status_id}")
async def update_custom_task_status(
project_id: int,
status_id: str,
status_data: CustomTaskStatusUpdate,
db: Session = Depends(get_db),
current_user: User = Depends(require_coordinator_or_admin)
):
"""Update a custom task status"""
# Implementation details...
@router.delete("/{project_id}/custom-task-statuses/{status_id}")
async def delete_custom_task_status(
project_id: int,
status_id: str,
delete_data: CustomTaskStatusDelete,
db: Session = Depends(get_db),
current_user: User = Depends(require_coordinator_or_admin)
):
"""Delete a custom task status"""
# Implementation details...
@router.patch("/{project_id}/custom-task-statuses/reorder")
async def reorder_custom_task_statuses(
project_id: int,
reorder_data: CustomTaskStatusReorder,
db: Session = Depends(get_db),
current_user: User = Depends(require_coordinator_or_admin)
):
"""Reorder custom task statuses"""
# Implementation details...
```
### Frontend Components
#### 1. Custom Task Status Manager Component
**Component** (`frontend/src/components/settings/CustomTaskStatusManager.vue`):
```vue
Task Statuses
Customize task statuses to match your production workflow
```
#### 2. Status Display Components
**TaskStatusBadge Enhancement** (`frontend/src/components/task/TaskStatusBadge.vue`):
```vue
{{ statusName }}
```
#### 3. Services
**Custom Task Status Service** (`frontend/src/services/customTaskStatus.ts`):
```typescript
export interface CustomTaskStatus {
id: string
name: string
color: string
order: number
is_default: boolean
}
export interface AllTaskStatusesResponse {
statuses: CustomTaskStatus[]
system_statuses: Array<{id: string, name: string, color: string}>
default_status_id: string
}
export const customTaskStatusService = {
async getAllStatuses(projectId: number): Promise {
// GET /api/projects/{projectId}/custom-task-statuses
},
async createStatus(projectId: number, data: {name: string, color?: string}): Promise {
// POST /api/projects/{projectId}/custom-task-statuses
},
async updateStatus(projectId: number, statusId: string, data: Partial): Promise {
// PUT /api/projects/{projectId}/custom-task-statuses/{statusId}
},
async deleteStatus(projectId: number, statusId: string, reassignTo?: string): Promise {
// DELETE /api/projects/{projectId}/custom-task-statuses/{statusId}
},
async reorderStatuses(projectId: number, statusIds: string[]): Promise {
// PATCH /api/projects/{projectId}/custom-task-statuses/reorder
}
}
```
## Data Models
### Custom Task Status Data Structure
```typescript
interface CustomTaskStatus {
id: string // Unique identifier (e.g., "custom_status_1")
name: string // Display name (e.g., "In Review")
color: string // Hex color code (e.g., "#FFA500")
order: number // Display order (0-based)
is_default: boolean // Whether this is the default status for new tasks
}
```
### Project Custom Statuses Storage
Stored in `Project.custom_task_statuses` as JSON:
```json
[
{
"id": "custom_status_1",
"name": "In Review",
"color": "#FFA500",
"order": 0,
"is_default": false
},
{
"id": "custom_status_2",
"name": "Client Feedback",
"color": "#9333EA",
"order": 1,
"is_default": false
}
]
```
### Task Status Reference
Tasks will store status as a string that can be either:
- System status key: `"not_started"`, `"in_progress"`, `"submitted"`, `"approved"`, `"retake"`
- Custom status ID: `"custom_status_1"`, `"custom_status_2"`, etc.
## Correctness Properties
*A property is a characteristic or behavior that should hold true across all valid executions of a system-essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*
### Property 1: Status name uniqueness within project
*For any* project and any two statuses within that project, the status names must be unique (case-insensitive comparison)
**Validates: Requirements 1.3, 2.4**
### Property 2: Status color format validity
*For any* custom status, if a color is specified, it must be a valid 6-digit hexadecimal color code starting with #
**Validates: Requirements 1.4**
### Property 3: Default status uniqueness
*For any* project, at most one status can be marked as the default status
**Validates: Requirements 5.2**
### Property 4: Status deletion with task reassignment
*For any* status deletion where tasks exist, all tasks using the deleted status must be reassigned to another valid status before deletion completes
**Validates: Requirements 3.4, 3.5**
### Property 5: Status order consistency
*For any* project's status list, the order values must form a continuous sequence from 0 to n-1 where n is the number of statuses
**Validates: Requirements 4.1, 4.3**
### Property 6: Task status reference validity
*For any* task, the status field must reference either a valid system status or a valid custom status from the task's project
**Validates: Requirements 6.4, 6.5**
### Property 7: Status update propagation
*For any* status name or color update, all UI components displaying that status must reflect the new values without requiring a page refresh
**Validates: Requirements 2.3, 7.5**
### Property 8: Project isolation
*For any* two different projects, custom statuses defined in one project must not be accessible or visible in the other project
**Validates: Requirements 9.1, 9.2, 9.3**
### Property 9: Backward compatibility
*For any* existing task with a system status, the task must continue to display and function correctly after the custom status feature is deployed
**Validates: Requirements 6.1, 6.2**
### Property 10: Bulk status update validity
*For any* bulk status update operation, all selected tasks must belong to the same project and the target status must be valid for that project
**Validates: Requirements 10.1, 10.2**
## Error Handling
### Validation Errors
1. **Duplicate Status Name**
- HTTP 409 Conflict
- Message: "A status with the name '{name}' already exists in this project"
- Frontend: Display inline error in dialog
2. **Invalid Color Format**
- HTTP 422 Unprocessable Entity
- Message: "Color must be a valid hex code (e.g., #FF5733)"
- Frontend: Validate on input, show error message
3. **Status In Use**
- HTTP 422 Unprocessable Entity
- Response includes: task_count, task_ids
- Frontend: Show reassignment dialog with task count
4. **Invalid Status Reference**
- HTTP 404 Not Found
- Message: "Status '{status_id}' not found"
- Frontend: Refresh status list, show error toast
### Business Logic Errors
1. **Cannot Delete Default Status**
- Automatically reassign default to first remaining status
- Notify user of the change
2. **Cannot Delete Last Status**
- HTTP 400 Bad Request
- Message: "Cannot delete the last status. At least one status must exist."
3. **Reorder with Missing Status IDs**
- HTTP 400 Bad Request
- Message: "Reorder operation must include all existing status IDs"
### Database Errors
1. **Concurrent Modification**
- Use optimistic locking or retry logic
- HTTP 409 Conflict
- Message: "Status was modified by another user. Please refresh and try again."
2. **JSON Field Corruption**
- Validate JSON structure on read
- Fall back to empty array if corrupted
- Log error for investigation
## Testing Strategy
### Unit Tests
1. **Backend Unit Tests** (`backend/test_custom_task_status.py`):
- Test status CRUD operations
- Test validation logic (name uniqueness, color format)
- Test status deletion with task reassignment
- Test reordering logic
- Test default status management
- Test project isolation
2. **Frontend Unit Tests** (`frontend/src/components/settings/CustomTaskStatusManager.test.ts`):
- Test component rendering
- Test dialog interactions
- Test drag-and-drop reordering
- Test color picker functionality
- Test validation error display
### Integration Tests
1. **API Integration Tests**:
- Test complete status lifecycle (create → update → delete)
- Test status usage in task creation and updates
- Test bulk status updates with custom statuses
- Test status display across different views
2. **E2E Tests** (`frontend/test-custom-task-status.html`):
- Test creating a custom status and using it on a task
- Test editing a status and verifying UI updates
- Test deleting a status with reassignment
- Test reordering statuses via drag-and-drop
- Test setting default status
### Property-Based Tests
Property-based tests will use Python's `hypothesis` library for backend testing and `fast-check` for frontend testing where applicable.
1. **Property Test: Status name uniqueness** (Property 1)
- Generate random status names
- Attempt to create statuses with duplicate names
- Verify all rejections are correct
2. **Property Test: Color format validity** (Property 2)
- Generate random color strings (valid and invalid)
- Verify only valid hex codes are accepted
3. **Property Test: Status order consistency** (Property 5)
- Generate random reorder operations
- Verify order values remain continuous
4. **Property Test: Task status reference validity** (Property 6)
- Generate random task status assignments
- Verify all references resolve correctly
### Manual Testing Checklist
- [ ] Create custom status with auto-assigned color
- [ ] Create custom status with specific color
- [ ] Edit status name and verify UI updates everywhere
- [ ] Edit status color and verify badge updates
- [ ] Set a status as default and create new task
- [ ] Reorder statuses via drag-and-drop
- [ ] Delete unused status
- [ ] Attempt to delete status in use (verify error)
- [ ] Delete status with reassignment
- [ ] Use custom status in bulk update
- [ ] Verify status isolation between projects
- [ ] Verify backward compatibility with existing tasks
## Implementation Notes
### Migration Strategy
1. **Phase 1: Database Schema**
- Add `custom_task_statuses` JSON column to Project model
- Create migration script to add column with default empty array
- No changes to existing task statuses
2. **Phase 2: Backend API**
- Implement custom status CRUD endpoints
- Update task endpoints to support custom status references
- Maintain backward compatibility with system statuses
3. **Phase 3: Frontend Components**
- Create CustomTaskStatusManager component
- Update TaskStatusBadge to support custom colors
- Update status dropdowns to include custom statuses
- Update filters to include custom statuses
4. **Phase 4: Testing & Rollout**
- Run comprehensive test suite
- Deploy to staging environment
- Conduct user acceptance testing
- Deploy to production
### Backward Compatibility
- Existing tasks with system statuses will continue to work
- System statuses will always be available alongside custom statuses
- Status resolution logic: Check if status is system status first, then check custom statuses
- If a task has an invalid status reference, fall back to "not_started"
### Performance Considerations
- Custom statuses stored as JSON in Project table (minimal overhead)
- Status resolution happens in-memory (no additional queries)
- Consider caching project statuses in frontend store
- Bulk operations should batch database updates
### Security Considerations
- Only coordinators, project managers, and admins can manage statuses
- Validate all status references before updating tasks
- Sanitize status names to prevent XSS
- Rate limit status management endpoints
### UI/UX Considerations
- Use color picker with predefined palette for consistency
- Show visual preview of status badge while editing
- Provide clear feedback when status is in use
- Use drag handles for intuitive reordering
- Display task count prominently when deleting
- Auto-select reassignment status when deleting
- Show loading states during async operations