# Track Specification: Manual Block/Unblock Control (manual_block_control_20260306)

## Overview
Allow user to manually block or unblock tickets with custom reasons. Currently blocked tickets rely solely on dependency resolution; add manual override capability.

## Current State Audit

### Already Implemented (DO NOT re-implement)

#### Ticket Status (src/models.py)
- **`Ticket` dataclass** has `status` field: "todo" | "in_progress" | "completed" | "blocked"
- **`blocked_reason` field**: `Optional[str]` - exists but only set by dependency cascade
- **`mark_blocked(reason: str)` method**: Sets status="blocked", stores reason

#### DAG Blocking (src/dag_engine.py)
- **`cascade_blocks()` method**: Transitively marks tickets as blocked when dependencies are blocked
- **Dependency resolution**: Tickets blocked if any `depends_on` is not "completed"
- **No manual override exists**

#### GUI Display (src/gui_2.py)
- **`_render_ticket_dag_node()`**: Renders ticket nodes with status colors
- **Blocked nodes shown in distinct color**
- **No block/unblock buttons**

### Gaps to Fill (This Track's Scope)
- No way to manually set blocked status
- No way to add custom block reason
- No way to manually unblock (clear blocked status)
- Visual indicator for manual vs dependency blocking

## Architectural Constraints

### DAG Validity
- Manual block MUST trigger cascade to downstream tickets
- Manual unblock MUST check dependencies are satisfied
- Cannot unblock if dependencies still blocked

### Audit Trail
- Block reason MUST be stored in Ticket
- Distinguish manual vs dependency blocking

### State Synchronization
- Block/unblock MUST update GUI immediately
- MUST persist to track state

## Architecture Reference

### Key Integration Points

| File | Lines | Purpose |
|------|-------|---------|
| `src/models.py` | 40-60 | `Ticket.mark_blocked()`, `blocked_reason` |
| `src/dag_engine.py` | 30-50 | `cascade_blocks()` - call after manual block |
| `src/gui_2.py` | 2700-2800 | `_render_ticket_dag_node()` - add buttons |
| `src/project_manager.py` | 238-260 | Track state persistence |

### Proposed Ticket Enhancement

```python
# Add to Ticket dataclass:
manual_block: bool = False  # True if blocked manually, False if dependency

def mark_manual_block(self, reason: str) -> None:
 self.status = "blocked"
 self.blocked_reason = f"[MANUAL] {reason}"
 self.manual_block = True

def clear_manual_block(self) -> None:
 if self.manual_block:
  self.status = "todo"
  self.blocked_reason = None
  self.manual_block = False
```

## Functional Requirements

### FR1: Block Button
- Button on each ticket node to block
- Opens text input for block reason
- Sets `manual_block=True`, calls `mark_manual_block()`

### FR2: Unblock Button
- Button on blocked tickets to unblock
- Only enabled if dependencies are satisfied
- Clears manual block, sets status to "todo"

### FR3: Reason Display
- Show block reason on hover or in node
- Different visual for manual vs dependency block
- Show "[MANUAL]" prefix for manual blocks

### FR4: Cascade Integration
- Manual block triggers `cascade_blocks()`
- Manual unblock recalculates blocked status

## Non-Functional Requirements

| Requirement | Constraint |
|-------------|------------|
| Response Time | Block/unblock takes effect immediately |
| Persistence | Block state saved to track state |
| Visual Clarity | Manual blocks clearly distinguished |

## Testing Requirements

### Unit Tests
- Test `mark_manual_block()` sets correct fields
- Test `clear_manual_block()` restores todo status
- Test cascade after manual block

### Integration Tests (via `live_gui` fixture)
- Block ticket via GUI, verify status changes
- Unblock ticket, verify status restored
- Verify cascade affects downstream tickets

## Out of Scope
- Blocking during execution (kill first, then block)
- Scheduled/conditional blocking
- Block templates

## Acceptance Criteria
- [ ] Block button on each ticket
- [ ] Unblock button on blocked tickets
- [ ] Reason input saves to ticket
- [ ] Visual indicator distinguishes manual vs dependency
- [ ] Reason displayed in UI
- [ ] Cascade triggered on block/unblock
- [ ] State persisted to track state
- [ ] 1-space indentation maintained
