refinement of upcoming tracks
This commit is contained in:
@@ -1,21 +1,129 @@
|
||||
# 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 on dependency resolution; add manual override.
|
||||
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
|
||||
- **Clear Indication**: Manual blocks MUST be visually distinct.
|
||||
- **Audit Trail**: Block reason MUST be logged.
|
||||
|
||||
### 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
|
||||
- **Block Button**: Manually block selected ticket.
|
||||
- **Unblock Button**: Remove manual block.
|
||||
- **Reason Field**: Enter custom block reason.
|
||||
- **Visual Indicator**: Blocked tickets clearly marked.
|
||||
|
||||
### 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 works.
|
||||
- [ ] Unblock button works.
|
||||
- [ ] Reason field saves.
|
||||
- [ ] Visual indicator shows blocked status.
|
||||
- [ ] Reason displayed in UI.
|
||||
- [ ] 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
|
||||
|
||||
Reference in New Issue
Block a user