Init Repo

This commit is contained in:
2026-02-28 03:22:04 +08:00
commit de59b57ee7
883 changed files with 156857 additions and 0 deletions
@@ -0,0 +1,41 @@
# TaskBrowser Bulk Actions Feature Spec
## Overview
This spec defines the multi-selection and bulk action capabilities for the TaskBrowser component, enabling users to select multiple tasks and perform batch operations like status updates and assignments through a context menu.
## Spec Files
- **requirements.md** - User stories and acceptance criteria following EARS patterns
- **design.md** - Technical design with architecture, components, and correctness properties
- **tasks.md** - Implementation task list with 14 main tasks
## Key Features
1. **Multi-selection with checkboxes** - Select individual tasks or all tasks at once
2. **Selection count display** - Shows how many tasks are currently selected
3. **Right-click context menu** - Access bulk actions via context menu
4. **Bulk status updates** - Change status for multiple tasks simultaneously
5. **Bulk task assignment** - Assign multiple tasks to a user at once
6. **Keyboard shortcuts** - Ctrl+A, Escape, Ctrl+Click, Shift+Click support
## Technology Stack
- **Frontend**: Vue 3, TanStack Table (row selection), shadcn-vue (DropdownMenu)
- **Backend**: FastAPI with new bulk action endpoints
- **Testing**: fast-check for property-based testing (optional tasks)
## Getting Started
To begin implementation:
1. Open `tasks.md` in the Kiro IDE
2. Click "Start task" next to Task 1 to begin
3. Follow the tasks sequentially for best results
## Status
✅ Requirements - Approved
✅ Design - Approved
✅ Tasks - Approved (with optional tests)
⏳ Implementation - Ready to start
@@ -0,0 +1,563 @@
# 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)
@@ -0,0 +1,95 @@
# Requirements Document
## Introduction
This specification defines the multi-selection and bulk action capabilities for the TaskBrowser component in the VFX Project Management System. The feature enables users to select multiple tasks simultaneously and perform batch operations such as status updates and assignment changes through a context menu interface.
## Glossary
- **TaskBrowser**: The data table component that displays tasks in a tabular format with filtering and sorting capabilities
- **Multi-selection**: The ability to select multiple rows (tasks) in the data table simultaneously using checkboxes
- **Context Menu**: A right-click dropdown menu that appears when tasks are selected, providing bulk action options
- **Bulk Action**: An operation that applies to multiple selected tasks simultaneously
- **Task Status**: The current state of a task (e.g., Not Started, In Progress, Complete, On Hold)
- **Task Assignment**: The association of a task with a specific user who is responsible for completing it
## Requirements
### Requirement 1
**User Story:** As a coordinator, I want to select multiple tasks in the TaskBrowser, so that I can perform bulk operations efficiently without updating tasks one by one.
#### Acceptance Criteria
1. WHEN the TaskBrowser loads THEN the system SHALL display a checkbox column as the first column in the data table
2. WHEN a user clicks a row checkbox THEN the system SHALL toggle the selection state for that specific task
3. WHEN a user clicks the header checkbox THEN the system SHALL toggle selection for all visible tasks in the current filtered view
4. WHEN tasks are selected THEN the system SHALL provide visual feedback by highlighting selected rows
5. WHEN the filter or search criteria changes THEN the system SHALL clear all current selections
### Requirement 2
**User Story:** As a coordinator, I want to see how many tasks I have selected, so that I can confirm the scope of my bulk action before executing it.
#### Acceptance Criteria
1. WHEN no tasks are selected THEN the system SHALL display the normal task count information
2. WHEN one or more tasks are selected THEN the system SHALL display the count of selected tasks prominently
3. WHEN tasks are selected THEN the system SHALL update the selection count immediately upon selection changes
### Requirement 3
**User Story:** As a coordinator, I want to right-click on selected tasks to open a context menu, so that I can access bulk action options intuitively.
#### Acceptance Criteria
1. WHEN a user right-clicks on a selected task row THEN the system SHALL display a context menu at the cursor position
2. WHEN a user right-clicks on an unselected task row THEN the system SHALL select that task and display the context menu
3. WHEN the context menu is open and the user clicks outside THEN the system SHALL close the context menu
4. WHEN no tasks are selected and the user right-clicks empty space THEN the system SHALL not display the context menu
### Requirement 4
**User Story:** As a coordinator, I want to change the status of multiple tasks at once through the context menu, so that I can efficiently update task progress across the project.
#### Acceptance Criteria
1. WHEN the context menu opens THEN the system SHALL display a "Set Status" option with a submenu of available status values
2. WHEN a user selects a status from the submenu THEN the system SHALL update all selected tasks to that status
3. WHEN the bulk status update completes successfully THEN the system SHALL display a success notification indicating the number of tasks updated
4. WHEN the bulk status update fails THEN the system SHALL display an error notification and maintain the original task states
5. WHEN the status update completes THEN the system SHALL refresh the task list to reflect the changes
### Requirement 5
**User Story:** As a coordinator, I want to assign multiple tasks to a user through the context menu, so that I can efficiently distribute work across the team.
#### Acceptance Criteria
1. WHEN the context menu opens THEN the system SHALL display an "Assign To" option with a submenu of available users
2. WHEN the "Assign To" submenu opens THEN the system SHALL display all project members who can be assigned tasks
3. WHEN a user selects an assignee from the submenu THEN the system SHALL update all selected tasks to be assigned to that user
4. WHEN the bulk assignment completes successfully THEN the system SHALL display a success notification indicating the number of tasks assigned
5. WHEN the bulk assignment fails THEN the system SHALL display an error notification and maintain the original assignments
6. WHEN the assignment update completes THEN the system SHALL refresh the task list to reflect the changes
### Requirement 6
**User Story:** As a coordinator, I want the context menu to close automatically after I perform an action, so that the interface remains clean and I can see the results of my operation.
#### Acceptance Criteria
1. WHEN a user completes a bulk action from the context menu THEN the system SHALL close the context menu automatically
2. WHEN a bulk action is in progress THEN the system SHALL disable the context menu options to prevent duplicate operations
3. WHEN a bulk action completes THEN the system SHALL clear the task selections
### Requirement 7
**User Story:** As a user, I want keyboard shortcuts for selection operations, so that I can work more efficiently without relying solely on mouse interactions.
#### Acceptance Criteria
1. WHEN a user presses Ctrl+A (or Cmd+A on Mac) while focused on the table THEN the system SHALL select all visible tasks
2. WHEN a user presses Escape while tasks are selected THEN the system SHALL clear all selections
3. WHEN a user clicks a task while holding Ctrl (or Cmd on Mac) THEN the system SHALL toggle that task's selection without affecting other selections
4. WHEN a user clicks a task while holding Shift THEN the system SHALL select all tasks between the last selected task and the clicked task
@@ -0,0 +1,179 @@
# Implementation Plan
- [x] 1. Set up backend bulk action endpoints
- Create bulk status update endpoint in `backend/routers/tasks.py`
- Create bulk assignment endpoint in `backend/routers/tasks.py`
- Implement transaction handling for atomicity
- Add request/response schemas in `backend/schemas/task.py`
- _Requirements: 4.2, 4.4, 5.3, 5.5_
- [ ]* 1.1 Write property test for bulk status update atomicity
- **Property 4: Bulk status update atomicity**
- **Validates: Requirements 4.2, 4.4**
- [ ]* 1.2 Write property test for bulk assignment atomicity
- **Property 5: Bulk assignment atomicity**
- **Validates: Requirements 5.3, 5.5**
- [x] 2. Update task service with bulk action methods
- Add `bulkUpdateStatus` method to `frontend/src/services/task.ts`
- Add `bulkAssignTasks` method to `frontend/src/services/task.ts`
- Define TypeScript interfaces for bulk action requests and responses
- _Requirements: 4.2, 5.3_
- [x] 3. Add checkbox selection column to TaskBrowser
- Update `frontend/src/components/task/columns.ts` to add select column
- Implement header checkbox for select all functionality
- Implement row checkboxes for individual selection
- Ensure checkbox column is not sortable or hideable
- _Requirements: 1.1, 1.2, 1.3_
- [ ]* 3.1 Write property test for selection state consistency
- **Property 1: Selection state consistency**
- **Validates: Requirements 1.2, 1.3, 2.3**
- [x] 4. Implement row selection state in TaskBrowser
- Add `rowSelection` state using TanStack Table's row selection
- Configure table with `enableRowSelection: true`
- Add computed property for `selectedTasks` array
- Add computed property for `selectedCount`
- Implement visual feedback for selected rows (background highlight)
- _Requirements: 1.2, 1.3, 1.4, 2.1, 2.2, 2.3_
- [x] 5. Implement selection count display
- Update task count display area to show selection count when tasks are selected
- Show format: "X tasks selected" when selection is active
- Show normal count when no selection
- _Requirements: 2.1, 2.2, 2.3_
- [x] 6. Implement filter-based selection clearing
- Add watchers for filter changes (status, type, episode, assignee, context, search)
- Clear `rowSelection` state when any filter changes
- _Requirements: 1.5_
- [ ]* 6.1 Write property test for filter clears selection
- **Property 2: Filter clears selection**
- **Validates: Requirements 1.5**
- [x] 7. Create TaskBulkActionsMenu component
- Create new component at `frontend/src/components/task/TaskBulkActionsMenu.vue`
- Use DropdownMenu from shadcn-vue for base structure
- Implement absolute positioning based on cursor coordinates
- Add viewport boundary detection for menu positioning
- Create "Set Status" menu item with status submenu
- Create "Assign To" menu item with user list submenu
- Emit events for status-selected and assignee-selected
- _Requirements: 3.1, 3.3, 4.1, 5.1, 5.2_
- [x] 8. Implement context menu trigger in TaskBrowser
- Add `@contextmenu` event handler to table rows
- Implement `handleContextMenu` method to position and show menu
- Ensure right-clicked unselected row gets selected before menu shows
- Add click-outside handler to close context menu
- Prevent context menu on empty table areas
- _Requirements: 3.1, 3.2, 3.3, 3.4_
- [ ]* 8.1 Write property test for context menu task inclusion
- **Property 3: Context menu task inclusion**
- **Validates: Requirements 3.2**
- [x] 9. Implement bulk status update action
- Create `handleBulkStatusUpdate` method in TaskBrowser
- Extract selected task IDs from selection state
- Call `taskService.bulkUpdateStatus` with task IDs and new status
- Show loading state during operation
- Display success toast with count of updated tasks
- Handle errors and display error toast
- Refresh task list after successful update
- Close context menu and clear selection after completion
- _Requirements: 4.2, 4.3, 4.4, 4.5, 6.1, 6.3_
- [x] 10. Implement bulk assignment action
- Create `handleBulkAssignment` method in TaskBrowser
- Extract selected task IDs from selection state
- Call `taskService.bulkAssignTasks` with task IDs and user ID
- Show loading state during operation
- Display success toast with count of assigned tasks
- Handle errors and display error toast
- Refresh task list after successful update
- Close context menu and clear selection after completion
- _Requirements: 5.3, 5.4, 5.5, 5.6, 6.1, 6.3_
- [ ] 11. Implement keyboard shortcuts
- Add `@keydown` event handler to table container
- Implement Ctrl+A (Cmd+A on Mac) to select all visible tasks
- Implement Escape to clear selection and close context menu
- Implement Ctrl+Click (Cmd+Click on Mac) for toggle selection
- Implement Shift+Click for range selection
- Track `lastSelectedIndex` for range selection
- _Requirements: 7.1, 7.2, 7.3, 7.4_
- [ ]* 11.1 Write property test for Ctrl+A selection
- **Property 6: Keyboard shortcut selection**
- **Validates: Requirements 7.1**
- [ ]* 11.2 Write property test for Shift-click range selection
- **Property 7: Shift-click range selection**
- **Validates: Requirements 7.4**
- [ ] 12. Add loading and disabled states
- Add `isProcessingBulkAction` state flag
- Disable context menu options during bulk operations
- Show loading spinner or disabled state in menu
- Prevent duplicate operations while processing
- _Requirements: 6.2_
- [ ]* 13. Write unit tests for edge cases
- Test empty selection state handling
- Test single task selection
- Test select all with no tasks
- Test context menu positioning at viewport edges
- Test keyboard event handling with various modifier keys
- [ ] 14. Final checkpoint - Ensure all tests pass
- Ensure all tests pass, ask the user if questions arise.