LinkDesk/.kiro/specs/vfx-project-management/custom-task-status-design.md

23 KiB

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):

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):

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):

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):

# 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):

<template>
  <div class="space-y-6">
    <div>
      <h3 class="text-lg font-semibold">Task Statuses</h3>
      <p class="text-sm text-muted-foreground mt-1">
        Customize task statuses to match your production workflow
      </p>
    </div>

    <!-- Status List with Drag-and-Drop -->
    <div class="space-y-2">
      <draggable
        v-model="statusList"
        @end="handleReorder"
        handle=".drag-handle"
        item-key="id"
      >
        <template #item="{ element: status }">
          <div class="status-item">
            <!-- Drag handle, status badge, edit/delete buttons -->
          </div>
        </template>
      </draggable>
    </div>

    <!-- Add Status Button -->
    <Button @click="openAddDialog">
      <Plus class="h-4 w-4 mr-2" />
      Add Status
    </Button>

    <!-- Add/Edit Dialog -->
    <Dialog v-model:open="isDialogOpen">
      <!-- Status name input, color picker -->
    </Dialog>

    <!-- Delete Confirmation Dialog -->
    <AlertDialog v-model:open="isDeleteDialogOpen">
      <!-- Confirmation with task count and reassignment option -->
    </AlertDialog>
  </div>
</template>

2. Status Display Components

TaskStatusBadge Enhancement (frontend/src/components/task/TaskStatusBadge.vue):

<template>
  <Badge
    :style="{ 
      backgroundColor: statusColor,
      color: getContrastColor(statusColor)
    }"
  >
    {{ statusName }}
  </Badge>
</template>

<script setup lang="ts">
// Resolve status from system or custom statuses
// Apply custom color
// Calculate contrast color for text
</script>

3. Services

Custom Task Status Service (frontend/src/services/customTaskStatus.ts):

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<AllTaskStatusesResponse> {
    // GET /api/projects/{projectId}/custom-task-statuses
  },
  
  async createStatus(projectId: number, data: {name: string, color?: string}): Promise<AllTaskStatusesResponse> {
    // POST /api/projects/{projectId}/custom-task-statuses
  },
  
  async updateStatus(projectId: number, statusId: string, data: Partial<CustomTaskStatus>): Promise<AllTaskStatusesResponse> {
    // PUT /api/projects/{projectId}/custom-task-statuses/{statusId}
  },
  
  async deleteStatus(projectId: number, statusId: string, reassignTo?: string): Promise<AllTaskStatusesResponse> {
    // DELETE /api/projects/{projectId}/custom-task-statuses/{statusId}
  },
  
  async reorderStatuses(projectId: number, statusIds: string[]): Promise<AllTaskStatusesResponse> {
    // PATCH /api/projects/{projectId}/custom-task-statuses/reorder
  }
}

Data Models

Custom Task Status Data Structure

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:

[
  {
    "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