Major fixes and improvements: Trade System Fixes: - Fix duplicate player moves in trade embed Player Exchanges section - Resolve "WVMiL not participating" error for Minor League destinations - Implement organizational authority model for ML/MiL/IL team relationships - Update Trade.cross_team_moves to deduplicate using moves_giving only Team Model Enhancements: - Rewrite roster_type() method using sname as definitive source per spec - Fix edge cases like "BHMIL" (Birmingham IL) vs "BHMMIL" - Update _get_base_abbrev() to use consistent sname-based logic - Add organizational lookup support in trade participation Autocomplete System: - Fix major_league_team_autocomplete invalid roster_type parameter - Implement client-side filtering using Team.roster_type() method - Add comprehensive test coverage for all autocomplete functions - Centralize autocomplete logic to shared utils functions Test Infrastructure: - Add 25 new tests for trade models and trade builder - Add 13 autocomplete function tests with error handling - Fix existing test failures with proper mocking patterns - Update dropadd tests to use shared autocomplete functions Documentation Updates: - Document trade model enhancements and deduplication fix - Add autocomplete function documentation with usage examples - Document organizational authority model and edge case handling - Update README files with recent fixes and implementation notes 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
508 lines
15 KiB
Markdown
508 lines
15 KiB
Markdown
# Views Directory
|
||
|
||
The views directory contains Discord UI components for Discord Bot v2.0, providing consistent visual interfaces and interactive elements. This includes embeds, modals, buttons, select menus, and other Discord UI components.
|
||
|
||
## Architecture
|
||
|
||
### Component-Based UI Design
|
||
Views in Discord Bot v2.0 follow these principles:
|
||
- **Consistent styling** via centralized templates
|
||
- **Reusable components** for common UI patterns
|
||
- **Error handling** with graceful degradation
|
||
- **User interaction tracking** and validation
|
||
- **Accessibility** with proper labeling and feedback
|
||
|
||
### Base Components
|
||
All view components inherit from Discord.py base classes with enhanced functionality:
|
||
- **BaseView** - Enhanced discord.ui.View with logging and user validation
|
||
- **BaseModal** - Enhanced discord.ui.Modal with error handling
|
||
- **EmbedTemplate** - Centralized embed creation with consistent styling
|
||
|
||
## View Components
|
||
|
||
### Base View System (`base.py`)
|
||
|
||
#### BaseView Class
|
||
Foundation for all interactive views:
|
||
|
||
```python
|
||
class BaseView(discord.ui.View):
|
||
def __init__(self, timeout=180.0, user_id=None):
|
||
super().__init__(timeout=timeout)
|
||
self.user_id = user_id
|
||
self.logger = get_contextual_logger(f'{__name__}.BaseView')
|
||
|
||
async def interaction_check(self, interaction) -> bool:
|
||
"""Validate user permissions for interaction."""
|
||
|
||
async def on_timeout(self) -> None:
|
||
"""Handle view timeout gracefully."""
|
||
|
||
async def on_error(self, interaction, error, item) -> None:
|
||
"""Handle view errors with user feedback."""
|
||
```
|
||
|
||
#### ConfirmationView Class
|
||
Standard Yes/No confirmation dialogs:
|
||
|
||
```python
|
||
confirmation = ConfirmationView(
|
||
user_id=interaction.user.id,
|
||
confirm_callback=handle_confirm,
|
||
cancel_callback=handle_cancel
|
||
)
|
||
await interaction.followup.send("Confirm action?", view=confirmation)
|
||
```
|
||
|
||
#### PaginationView Class
|
||
Multi-page navigation for large datasets:
|
||
|
||
```python
|
||
pages = [embed1, embed2, embed3]
|
||
pagination = PaginationView(
|
||
pages=pages,
|
||
user_id=interaction.user.id,
|
||
show_page_numbers=True
|
||
)
|
||
await interaction.followup.send(embed=pagination.get_current_embed(), view=pagination)
|
||
```
|
||
|
||
### Embed Templates (`embeds.py`)
|
||
|
||
#### EmbedTemplate Class
|
||
Centralized embed creation with consistent styling:
|
||
|
||
```python
|
||
# Success embed
|
||
embed = EmbedTemplate.success(
|
||
title="Operation Completed",
|
||
description="Your request was processed successfully."
|
||
)
|
||
|
||
# Error embed
|
||
embed = EmbedTemplate.error(
|
||
title="Operation Failed",
|
||
description="Please check your input and try again."
|
||
)
|
||
|
||
# Warning embed
|
||
embed = EmbedTemplate.warning(
|
||
title="Careful!",
|
||
description="This action cannot be undone."
|
||
)
|
||
|
||
# Info embed
|
||
embed = EmbedTemplate.info(
|
||
title="Information",
|
||
description="Here's what you need to know."
|
||
)
|
||
```
|
||
|
||
#### EmbedColors Dataclass
|
||
Consistent color scheme across all embeds:
|
||
|
||
```python
|
||
@dataclass(frozen=True)
|
||
class EmbedColors:
|
||
PRIMARY: int = 0xa6ce39 # SBA green
|
||
SUCCESS: int = 0x28a745 # Green
|
||
WARNING: int = 0xffc107 # Yellow
|
||
ERROR: int = 0xdc3545 # Red
|
||
INFO: int = 0x17a2b8 # Blue
|
||
SECONDARY: int = 0x6c757d # Gray
|
||
```
|
||
|
||
### Modal Forms (`modals.py`)
|
||
|
||
#### BaseModal Class
|
||
Foundation for interactive forms:
|
||
|
||
```python
|
||
class BaseModal(discord.ui.Modal):
|
||
def __init__(self, title: str, timeout=300.0):
|
||
super().__init__(title=title, timeout=timeout)
|
||
self.logger = get_contextual_logger(f'{__name__}.BaseModal')
|
||
self.result = None
|
||
|
||
async def on_submit(self, interaction):
|
||
"""Handle form submission."""
|
||
|
||
async def on_error(self, interaction, error):
|
||
"""Handle form errors."""
|
||
```
|
||
|
||
#### Usage Pattern
|
||
```python
|
||
class CustomCommandModal(BaseModal):
|
||
def __init__(self):
|
||
super().__init__(title="Create Custom Command")
|
||
|
||
name = discord.ui.TextInput(
|
||
label="Command Name",
|
||
placeholder="Enter command name...",
|
||
required=True,
|
||
max_length=50
|
||
)
|
||
|
||
response = discord.ui.TextInput(
|
||
label="Response",
|
||
placeholder="Enter command response...",
|
||
style=discord.TextStyle.paragraph,
|
||
required=True,
|
||
max_length=2000
|
||
)
|
||
|
||
async def on_submit(self, interaction):
|
||
# Process form data
|
||
command_data = {
|
||
"name": self.name.value,
|
||
"response": self.response.value
|
||
}
|
||
# Handle creation logic
|
||
```
|
||
|
||
### Common UI Elements (`common.py`)
|
||
|
||
#### Shared Components
|
||
- **Loading indicators** for async operations
|
||
- **Status messages** for operation feedback
|
||
- **Navigation elements** for multi-step processes
|
||
- **Validation displays** for form errors
|
||
|
||
### Specialized Views
|
||
|
||
#### Custom Commands (`custom_commands.py`)
|
||
Views specific to custom command management:
|
||
- Command creation forms
|
||
- Command listing with actions
|
||
- Bulk management interfaces
|
||
|
||
#### Transaction Management (`transaction_embed.py`)
|
||
Views for player transaction interfaces:
|
||
- Transaction builder with interactive controls
|
||
- Comprehensive validation and sWAR display
|
||
- Pre-existing transaction context
|
||
- Approval/submission workflows
|
||
|
||
## Styling Guidelines
|
||
|
||
### Embed Consistency
|
||
All embeds should use EmbedTemplate methods:
|
||
|
||
```python
|
||
# ✅ Consistent styling
|
||
embed = EmbedTemplate.success("Player Added", "Player successfully added to roster")
|
||
|
||
# ❌ Inconsistent styling
|
||
embed = discord.Embed(title="Player Added", color=0x00ff00)
|
||
```
|
||
|
||
### Color Usage
|
||
Use the standard color palette:
|
||
- **PRIMARY (SBA Green)** - Default for neutral information
|
||
- **SUCCESS (Green)** - Successful operations
|
||
- **ERROR (Red)** - Errors and failures
|
||
- **WARNING (Yellow)** - Warnings and cautions
|
||
- **INFO (Blue)** - General information
|
||
- **SECONDARY (Gray)** - Less important information
|
||
|
||
### User Feedback
|
||
Provide clear feedback for all user interactions:
|
||
|
||
```python
|
||
# Loading state
|
||
embed = EmbedTemplate.info("Processing", "Please wait while we process your request...")
|
||
|
||
# Success state
|
||
embed = EmbedTemplate.success("Complete", "Your request has been processed successfully.")
|
||
|
||
# Error state with helpful information
|
||
embed = EmbedTemplate.error(
|
||
"Request Failed",
|
||
"The player name was not found. Please check your spelling and try again."
|
||
)
|
||
```
|
||
|
||
## Interactive Components
|
||
|
||
### Button Patterns
|
||
|
||
#### Action Buttons
|
||
```python
|
||
@discord.ui.button(label="Confirm", style=discord.ButtonStyle.success, emoji="✅")
|
||
async def confirm_button(self, interaction, button):
|
||
self.increment_interaction_count()
|
||
# Handle confirmation
|
||
await interaction.response.edit_message(content="Confirmed!", view=None)
|
||
```
|
||
|
||
#### Navigation Buttons
|
||
```python
|
||
@discord.ui.button(emoji="◀️", style=discord.ButtonStyle.primary)
|
||
async def previous_page(self, interaction, button):
|
||
self.current_page = max(0, self.current_page - 1)
|
||
await interaction.response.edit_message(embed=self.get_current_embed(), view=self)
|
||
```
|
||
|
||
### Select Menu Patterns
|
||
|
||
#### Option Selection
|
||
```python
|
||
@discord.ui.select(placeholder="Choose an option...")
|
||
async def select_option(self, interaction, select):
|
||
selected_value = select.values[0]
|
||
# Handle selection
|
||
await interaction.response.send_message(f"You selected: {selected_value}")
|
||
```
|
||
|
||
#### Dynamic Options
|
||
```python
|
||
class PlayerSelectMenu(discord.ui.Select):
|
||
def __init__(self, players: List[Player]):
|
||
options = [
|
||
discord.SelectOption(
|
||
label=player.name,
|
||
value=str(player.id),
|
||
description=f"{player.position} - {player.team.abbrev}"
|
||
)
|
||
for player in players[:25] # Discord limit
|
||
]
|
||
super().__init__(placeholder="Select a player...", options=options)
|
||
```
|
||
|
||
## Error Handling
|
||
|
||
### View Error Handling
|
||
All views implement comprehensive error handling:
|
||
|
||
```python
|
||
async def on_error(self, interaction, error, item):
|
||
"""Handle view errors gracefully."""
|
||
self.logger.error("View error", error=error, item_type=type(item).__name__)
|
||
|
||
try:
|
||
embed = EmbedTemplate.error(
|
||
"Interaction Error",
|
||
"Something went wrong. Please try again."
|
||
)
|
||
|
||
if not interaction.response.is_done():
|
||
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||
else:
|
||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||
except Exception as e:
|
||
self.logger.error("Failed to send error message", error=e)
|
||
```
|
||
|
||
### User Input Validation
|
||
Forms validate user input before processing:
|
||
|
||
```python
|
||
async def on_submit(self, interaction):
|
||
# Validate input
|
||
if len(self.name.value) < 2:
|
||
embed = EmbedTemplate.error(
|
||
"Invalid Input",
|
||
"Command name must be at least 2 characters long."
|
||
)
|
||
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||
return
|
||
|
||
# Process valid input
|
||
await self.create_command(interaction)
|
||
```
|
||
|
||
## Accessibility Features
|
||
|
||
### User-Friendly Labels
|
||
- **Clear button labels** with descriptive text
|
||
- **Helpful placeholders** in form fields
|
||
- **Descriptive error messages** with actionable guidance
|
||
- **Consistent emoji usage** for visual recognition
|
||
|
||
### Permission Validation
|
||
Views respect user permissions and provide appropriate feedback:
|
||
|
||
```python
|
||
async def interaction_check(self, interaction) -> bool:
|
||
"""Check if user can interact with this view."""
|
||
if self.user_id and interaction.user.id != self.user_id:
|
||
await interaction.response.send_message(
|
||
"❌ You cannot interact with this menu.",
|
||
ephemeral=True
|
||
)
|
||
return False
|
||
return True
|
||
```
|
||
|
||
## Performance Considerations
|
||
|
||
### View Lifecycle Management
|
||
- **Timeout handling** prevents orphaned views
|
||
- **Resource cleanup** in view destructors
|
||
- **Interaction tracking** for usage analytics
|
||
- **Memory management** for large datasets
|
||
|
||
### Efficient Updates
|
||
```python
|
||
# ✅ Efficient - Only update what changed
|
||
await interaction.response.edit_message(embed=new_embed, view=self)
|
||
|
||
# ❌ Inefficient - Sends new message
|
||
await interaction.response.send_message(embed=new_embed, view=new_view)
|
||
```
|
||
|
||
## Testing Strategies
|
||
|
||
### View Testing
|
||
```python
|
||
@pytest.mark.asyncio
|
||
async def test_confirmation_view():
|
||
view = ConfirmationView(user_id=123)
|
||
|
||
# Mock interaction
|
||
interaction = Mock()
|
||
interaction.user.id = 123
|
||
|
||
# Test button click
|
||
await view.confirm_button.callback(interaction)
|
||
|
||
assert view.result is True
|
||
```
|
||
|
||
### Modal Testing
|
||
```python
|
||
@pytest.mark.asyncio
|
||
async def test_custom_command_modal():
|
||
modal = CustomCommandModal()
|
||
|
||
# Set form values
|
||
modal.name.value = "test"
|
||
modal.response.value = "Test response"
|
||
|
||
# Mock interaction
|
||
interaction = Mock()
|
||
|
||
# Test form submission
|
||
await modal.on_submit(interaction)
|
||
|
||
# Verify processing
|
||
assert modal.result is not None
|
||
```
|
||
|
||
## Development Guidelines
|
||
|
||
### Creating New Views
|
||
1. **Inherit from base classes** for consistency
|
||
2. **Use EmbedTemplate** for all embed creation
|
||
3. **Implement proper error handling** in all interactions
|
||
4. **Add user permission checks** where appropriate
|
||
5. **Include comprehensive logging** with context
|
||
6. **Follow timeout patterns** to prevent resource leaks
|
||
|
||
### View Composition
|
||
- **Keep views focused** on single responsibilities
|
||
- **Use composition** over complex inheritance
|
||
- **Separate business logic** from UI logic
|
||
- **Make views testable** with dependency injection
|
||
|
||
### UI Guidelines
|
||
- **Follow Discord design patterns** for familiarity
|
||
- **Use consistent colors** from EmbedColors
|
||
- **Provide clear user feedback** for all actions
|
||
- **Handle edge cases** gracefully
|
||
- **Consider mobile users** in layout design
|
||
|
||
## Transaction Embed Enhancements (January 2025)
|
||
|
||
### Enhanced Display Features
|
||
The transaction embed now provides comprehensive information for better decision-making:
|
||
|
||
#### New Embed Sections
|
||
```python
|
||
async def create_transaction_embed(builder: TransactionBuilder) -> discord.Embed:
|
||
"""
|
||
Creates enhanced transaction embed with sWAR and pre-existing transaction context.
|
||
"""
|
||
# Existing sections...
|
||
|
||
# NEW: Team Cost (sWAR) Display
|
||
swar_status = f"{validation.major_league_swar_status}\n{validation.minor_league_swar_status}"
|
||
embed.add_field(name="Team sWAR", value=swar_status, inline=False)
|
||
|
||
# NEW: Pre-existing Transaction Context (when applicable)
|
||
if validation.pre_existing_transactions_note:
|
||
embed.add_field(
|
||
name="📋 Transaction Context",
|
||
value=validation.pre_existing_transactions_note,
|
||
inline=False
|
||
)
|
||
```
|
||
|
||
### Enhanced Information Display
|
||
|
||
#### sWAR Tracking
|
||
- **Major League sWAR**: Projected team cost for ML roster
|
||
- **Minor League sWAR**: Projected team cost for MiL roster
|
||
- **Formatted Display**: Uses 📊 emoji with 1 decimal precision
|
||
|
||
#### Pre-existing Transaction Context
|
||
Dynamic context display based on scheduled moves:
|
||
|
||
```python
|
||
# Example displays:
|
||
"ℹ️ **Pre-existing Moves**: 3 scheduled moves (+3.7 sWAR)"
|
||
"ℹ️ **Pre-existing Moves**: 2 scheduled moves (-2.5 sWAR)"
|
||
"ℹ️ **Pre-existing Moves**: 1 scheduled moves (no sWAR impact)"
|
||
# No display when no pre-existing moves (clean interface)
|
||
```
|
||
|
||
### Complete Embed Structure
|
||
The enhanced transaction embed now includes:
|
||
|
||
1. **Current Moves** - List of moves in transaction builder
|
||
2. **Roster Status** - Legal/illegal roster counts with limits
|
||
3. **Team Cost (sWAR)** - sWAR for both rosters
|
||
4. **Transaction Context** - Pre-existing moves impact (conditional)
|
||
5. **Errors/Suggestions** - Validation feedback and recommendations
|
||
|
||
### Usage Examples
|
||
|
||
#### Basic Transaction Display
|
||
```python
|
||
# Standard transaction without pre-existing moves
|
||
builder = get_transaction_builder(user_id, team)
|
||
embed = await create_transaction_embed(builder)
|
||
# Shows: moves, roster status, sWAR, errors/suggestions
|
||
```
|
||
|
||
#### Enhanced Context Display
|
||
```python
|
||
# Transaction with pre-existing moves context
|
||
validation = await builder.validate_transaction(next_week=current_week + 1)
|
||
embed = await create_transaction_embed(builder)
|
||
# Shows: all above + pre-existing transaction impact
|
||
```
|
||
|
||
### User Experience Improvements
|
||
- **Complete Context**: Users see full impact including scheduled moves
|
||
- **Visual Clarity**: Consistent emoji usage and formatting
|
||
- **Conditional Display**: Context only shown when relevant
|
||
- **Decision Support**: sWAR projections help strategic planning
|
||
|
||
### Implementation Notes
|
||
- **Backwards Compatible**: Existing embed functionality preserved
|
||
- **Conditional Sections**: Pre-existing context only appears when applicable
|
||
- **Performance**: Validation data cached to avoid repeated calculations
|
||
- **Accessibility**: Clear visual hierarchy with emojis and formatting
|
||
|
||
---
|
||
|
||
**Next Steps for AI Agents:**
|
||
1. Review existing view implementations for patterns
|
||
2. Understand the Discord UI component system
|
||
3. Follow the EmbedTemplate system for consistent styling
|
||
4. Implement proper error handling and user validation
|
||
5. Test interactive components thoroughly
|
||
6. Consider accessibility and user experience in design
|
||
7. Leverage enhanced transaction context for better user guidance |