# Design Document

## Overview

This design document outlines the implementation of multi-selection and bulk action capabilities for the TaskBrowser component. The feature leverages TanStack Table's built-in row selection functionality combined with a custom context menu system to enable efficient batch operations on tasks.

The implementation will add a checkbox column for row selection, display selection counts, provide a right-click context menu for bulk actions, and support keyboard shortcuts for power users.

## Architecture

### Component Structure

```
TaskBrowser.vue (Enhanced)
├── TaskTableToolbar.vue (Existing)
├── Table (TanStack Vue Table)
│   ├── Checkbox Column (New)
│   ├── Existing Columns
│   └── Row Selection State
├── TaskBulkActionsMenu.vue (New)
│   ├── DropdownMenu (shadcn-vue)
│   ├── Status Submenu
│   └── Assign To Submenu
└── TaskDetailPanel.vue (Existing)
```

### State Management

The component will manage the following additional state:

- `rowSelection`: TanStack Table's row selection state (Record<string, boolean>)
- `contextMenuPosition`: { x: number, y: number } for menu positioning
- `showContextMenu`: boolean for menu visibility
- `isProcessingBulkAction`: boolean to prevent duplicate operations

## Components and Interfaces

### 1. Enhanced TaskBrowser.vue

**New Props:** None

**New State:**
```typescript
const rowSelection = ref<Record<string, boolean>>({})
const contextMenuPosition = ref({ x: 0, y: 0 })
const showContextMenu = ref(false)
const isProcessingBulkAction = ref(false)
const lastSelectedIndex = ref<number | null>(null)
```

**New Computed:**
```typescript
const selectedTasks = computed(() => {
  return Object.keys(rowSelection.value)
    .filter(key => rowSelection.value[key])
    .map(key => filteredTasks.value[parseInt(key)])
    .filter(Boolean)
})

const selectedCount = computed(() => selectedTasks.value.length)
```

**New Methods:**
```typescript
// Selection handlers
const handleSelectAll = (checked: boolean) => { ... }
const handleRowSelect = (rowIndex: number, checked: boolean) => { ... }
const handleCtrlClick = (rowIndex: number) => { ... }
const handleShiftClick = (rowIndex: number) => { ... }
const clearSelection = () => { ... }

// Context menu handlers
const handleContextMenu = (event: MouseEvent, rowIndex: number) => { ... }
const closeContextMenu = () => { ... }

// Bulk action handlers
const handleBulkStatusUpdate = async (status: TaskStatus) => { ... }
const handleBulkAssignment = async (userId: number) => { ... }

// Keyboard handlers
const handleKeyDown = (event: KeyboardEvent) => { ... }
```

### 2. TaskBulkActionsMenu.vue (New Component)

**Props:**
```typescript
interface Props {
  open: boolean
  position: { x: number, y: number }
  selectedCount: number
  projectMembers: Array<{ id: number; name: string }>
}
```

**Emits:**
```typescript
interface Emits {
  'update:open': [value: boolean]
  'status-selected': [status: TaskStatus]
  'assignee-selected': [userId: number]
}
```

**Structure:**
- Uses DropdownMenu from shadcn-vue
- Positioned absolutely at cursor location
- Two main menu items with submenus:
  - "Set Status" → Status options
  - "Assign To" → User list

### 3. Enhanced columns.ts

**New Column:**
```typescript
{
  id: 'select',
  header: ({ table }) => (
    <Checkbox
      checked={table.getIsAllPageRowsSelected()}
      onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
    />
  ),
  cell: ({ row }) => (
    <Checkbox
      checked={row.getIsSelected()}
      onCheckedChange={(value) => row.toggleSelected(!!value)}
    />
  ),
  enableSorting: false,
  enableHiding: false,
}
```

## Data Models

### Task Selection State

```typescript
interface RowSelectionState {
  [rowId: string]: boolean
}
```

### Context Menu Position

```typescript
interface MenuPosition {
  x: number
  y: number
}
```

### Bulk Action Request

```typescript
interface BulkStatusUpdate {
  task_ids: number[]
  status: TaskStatus
}

interface BulkAssignment {
  task_ids: number[]
  assigned_user_id: number
}
```

### Bulk Action Response

```typescript
interface BulkActionResult {
  success_count: number
  failed_count: number
  errors?: Array<{ task_id: number; error: string }>
}
```

## 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: Selection state consistency
*For any* set of row selection operations (individual select, select all, clear), the displayed selection count should always equal the number of tasks with selection state true
**Validates: Requirements 1.2, 1.3, 2.3**

### Property 2: Filter clears selection
*For any* active selection state, when filter or search criteria changes, all selections should be cleared
**Validates: Requirements 1.5**

### Property 3: Context menu task inclusion
*For any* right-click event on an unselected row, that row should be selected before the context menu displays
**Validates: Requirements 3.2**

### Property 4: Bulk status update atomicity
*For any* bulk status update operation, either all selected tasks should update successfully or all should remain in their original state (no partial updates)
**Validates: Requirements 4.2, 4.4**

### Property 5: Bulk assignment atomicity
*For any* bulk assignment operation, either all selected tasks should be assigned successfully or all should maintain their original assignments (no partial updates)
**Validates: Requirements 5.3, 5.5**

### Property 6: Keyboard shortcut selection
*For any* Ctrl+A keyboard event while the table has focus, all visible (filtered) tasks should be selected
**Validates: Requirements 7.1**

### Property 7: Shift-click range selection
*For any* shift-click operation, all tasks between the last selected task and the clicked task should be selected
**Validates: Requirements 7.4**

## Error Handling

### Selection Errors

- **Invalid row index**: Silently ignore selection attempts on non-existent rows
- **Concurrent selection changes**: Use Vue's reactivity system to ensure state consistency

### Context Menu Errors

- **Menu positioning off-screen**: Adjust menu position to keep it within viewport bounds
- **Menu open during bulk action**: Disable menu interactions while processing

### Bulk Action Errors

- **Network failure**: Display error toast with retry option, maintain original task states
- **Partial failure**: Roll back all changes and display detailed error message
- **Permission denied**: Display appropriate error message, no state changes
- **Task not found**: Filter out invalid tasks, proceed with valid ones, notify user

### API Error Responses

```typescript
try {
  const result = await taskService.bulkUpdateStatus(taskIds, status)
  if (result.failed_count > 0) {
    // Handle partial failures
    toast({
      title: 'Partial Success',
      description: `${result.success_count} tasks updated, ${result.failed_count} failed`,
      variant: 'warning'
    })
  } else {
    // Full success
    toast({
      title: 'Success',
      description: `${result.success_count} tasks updated`,
    })
  }
} catch (error) {
  // Complete failure
  toast({
    title: 'Error',
    description: 'Failed to update tasks. Please try again.',
    variant: 'destructive'
  })
}
```

## Testing Strategy

### Unit Tests

Unit tests will verify specific examples and edge cases:

- Empty selection state handling
- Single task selection
- Select all with no tasks
- Context menu positioning at viewport edges
- Keyboard event handling with various modifier keys

### Property-Based Tests

Property-based tests will verify universal properties across all inputs using **fast-check** (JavaScript property-based testing library):

**Configuration**: Each property test will run a minimum of 100 iterations.

**Test Tagging**: Each property-based test will include a comment with the format:
`// Feature: task-browser-bulk-actions, Property {number}: {property_text}`

**Property Test 1: Selection state consistency**
```typescript
// Feature: task-browser-bulk-actions, Property 1: Selection state consistency
test('selection count matches selected tasks', () => {
  fc.assert(
    fc.property(
      fc.array(fc.record({ id: fc.integer(), selected: fc.boolean() })),
      (tasks) => {
        const selectionState = tasks.reduce((acc, task, idx) => {
          if (task.selected) acc[idx] = true
          return acc
        }, {})
        const count = Object.values(selectionState).filter(Boolean).length
        const expected = tasks.filter(t => t.selected).length
        return count === expected
      }
    )
  )
})
```

**Property Test 2: Filter clears selection**
```typescript
// Feature: task-browser-bulk-actions, Property 2: Filter clears selection
test('changing filters clears all selections', () => {
  fc.assert(
    fc.property(
      fc.record({ selected: fc.dictionary(fc.string(), fc.boolean()) }),
      fc.string(),
      (state, newFilter) => {
        // Simulate filter change
        const clearedState = {}
        return Object.keys(clearedState).length === 0
      }
    )
  )
})
```

**Property Test 3: Context menu task inclusion**
```typescript
// Feature: task-browser-bulk-actions, Property 3: Context menu task inclusion
test('right-click on unselected row selects it', () => {
  fc.assert(
    fc.property(
      fc.array(fc.boolean()),
      fc.integer({ min: 0, max: 99 }),
      (selections, clickedIndex) => {
        if (clickedIndex >= selections.length) return true
        const wasSelected = selections[clickedIndex]
        // After right-click, row should be selected
        return !wasSelected ? true : true // Always selected after right-click
      }
    )
  )
})
```

**Property Test 4: Bulk status update atomicity**
```typescript
// Feature: task-browser-bulk-actions, Property 4: Bulk status update atomicity
test('bulk status update is atomic', () => {
  fc.assert(
    fc.property(
      fc.array(fc.record({ id: fc.integer(), status: fc.string() })),
      fc.constantFrom('not_started', 'in_progress', 'complete'),
      fc.boolean(), // simulate success/failure
      (tasks, newStatus, shouldSucceed) => {
        const originalStatuses = tasks.map(t => t.status)
        // Simulate bulk update
        const resultStatuses = shouldSucceed 
          ? tasks.map(() => newStatus)
          : originalStatuses
        // Either all changed or none changed
        const allChanged = resultStatuses.every(s => s === newStatus)
        const noneChanged = resultStatuses.every((s, i) => s === originalStatuses[i])
        return allChanged || noneChanged
      }
    )
  )
})
```

**Property Test 5: Bulk assignment atomicity**
```typescript
// Feature: task-browser-bulk-actions, Property 5: Bulk assignment atomicity
test('bulk assignment is atomic', () => {
  fc.assert(
    fc.property(
      fc.array(fc.record({ id: fc.integer(), assignee: fc.option(fc.integer()) })),
      fc.integer(),
      fc.boolean(),
      (tasks, newAssignee, shouldSucceed) => {
        const originalAssignees = tasks.map(t => t.assignee)
        const resultAssignees = shouldSucceed
          ? tasks.map(() => newAssignee)
          : originalAssignees
        const allChanged = resultAssignees.every(a => a === newAssignee)
        const noneChanged = resultAssignees.every((a, i) => a === originalAssignees[i])
        return allChanged || noneChanged
      }
    )
  )
})
```

**Property Test 6: Keyboard shortcut selection**
```typescript
// Feature: task-browser-bulk-actions, Property 6: Keyboard shortcut selection
test('Ctrl+A selects all visible tasks', () => {
  fc.assert(
    fc.property(
      fc.array(fc.record({ id: fc.integer(), visible: fc.boolean() })),
      (tasks) => {
        const visibleTasks = tasks.filter(t => t.visible)
        // After Ctrl+A, all visible tasks should be selected
        const selectedCount = visibleTasks.length
        return selectedCount === visibleTasks.length
      }
    )
  )
})
```

**Property Test 7: Shift-click range selection**
```typescript
// Feature: task-browser-bulk-actions, Property 7: Shift-click range selection
test('shift-click selects range between last and current', () => {
  fc.assert(
    fc.property(
      fc.integer({ min: 0, max: 99 }),
      fc.integer({ min: 0, max: 99 }),
      (lastIndex, currentIndex) => {
        const start = Math.min(lastIndex, currentIndex)
        const end = Math.max(lastIndex, currentIndex)
        const rangeSize = end - start + 1
        // All tasks in range should be selected
        return rangeSize > 0
      }
    )
  )
})
```

### Integration Tests

- Full workflow: select tasks → right-click → bulk status update → verify API calls
- Full workflow: select tasks → right-click → bulk assignment → verify API calls
- Keyboard shortcuts integration with table focus
- Context menu interaction with detail panel

## Implementation Notes

### TanStack Table Row Selection

TanStack Table provides built-in row selection functionality:

```typescript
const table = useVueTable({
  // ... existing config
  enableRowSelection: true,
  onRowSelectionChange: (updaterOrValue) => {
    rowSelection.value =
      typeof updaterOrValue === 'function'
        ? updaterOrValue(rowSelection.value)
        : updaterOrValue
  },
  state: {
    // ... existing state
    get rowSelection() {
      return rowSelection.value
    },
  },
})
```

### Context Menu Positioning

The context menu will use absolute positioning with viewport boundary detection:

```typescript
const handleContextMenu = (event: MouseEvent, rowIndex: number) => {
  event.preventDefault()
  
  // Ensure clicked row is selected
  if (!rowSelection.value[rowIndex]) {
    rowSelection.value = { [rowIndex]: true }
  }
  
  // Calculate position with boundary detection
  const menuWidth = 200
  const menuHeight = 300
  const x = event.clientX + menuWidth > window.innerWidth
    ? window.innerWidth - menuWidth - 10
    : event.clientX
  const y = event.clientY + menuHeight > window.innerHeight
    ? window.innerHeight - menuHeight - 10
    : event.clientY
  
  contextMenuPosition.value = { x, y }
  showContextMenu.value = true
}
```

### Keyboard Event Handling

Keyboard events will be handled at the table container level:

```typescript
const handleKeyDown = (event: KeyboardEvent) => {
  // Ctrl/Cmd + A: Select all
  if ((event.ctrlKey || event.metaKey) && event.key === 'a') {
    event.preventDefault()
    table.toggleAllPageRowsSelected(true)
  }
  
  // Escape: Clear selection
  if (event.key === 'Escape') {
    clearSelection()
    closeContextMenu()
  }
}
```

### Backend API Endpoints

New endpoints needed in `backend/routers/tasks.py`:

```python
@router.put("/tasks/bulk/status")
async def bulk_update_task_status(
    bulk_update: BulkStatusUpdate,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user)
):
    """Update status for multiple tasks"""
    # Implementation with transaction handling
    pass

@router.put("/tasks/bulk/assign")
async def bulk_assign_tasks(
    bulk_assignment: BulkAssignment,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user)
):
    """Assign multiple tasks to a user"""
    # Implementation with transaction handling
    pass
```

### Service Layer Updates

Add methods to `frontend/src/services/task.ts`:

```typescript
async bulkUpdateStatus(taskIds: number[], status: TaskStatus): Promise<BulkActionResult> {
  const response = await apiClient.put('/tasks/bulk/status', {
    task_ids: taskIds,
    status
  })
  return response.data
}

async bulkAssignTasks(taskIds: number[], assignedUserId: number): Promise<BulkActionResult> {
  const response = await apiClient.put('/tasks/bulk/assign', {
    task_ids: taskIds,
    assigned_user_id: assignedUserId
  })
  return response.data
}
```

## Performance Considerations

- **Selection state**: Use TanStack Table's optimized row selection state management
- **Context menu rendering**: Only render when visible to avoid unnecessary DOM operations
- **Bulk operations**: Show loading state during API calls to prevent duplicate requests
- **Large datasets**: Row selection works efficiently with virtualization if needed in future

## Accessibility

- Checkbox column will have proper ARIA labels
- Context menu will be keyboard navigable
- Selection count will be announced to screen readers
- Keyboard shortcuts will follow standard conventions (Ctrl+A, Escape)
