Files
sneakyklaus/docs/designs/v0.3.0/overview.md
Phil Skentelbery 915e77d994 chore: add production deployment config and upgrade path requirements
- Add docker-compose.yml and docker-compose.example.yml for production deployment
- Add .env.example with all required environment variables
- Update architect agent with upgrade path requirements
- Update developer agent with migration best practices
- Add Phase 3 design documents (v0.3.0)
- Add ADR-0006 for participant state management

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-22 19:32:42 -07:00

567 lines
18 KiB
Markdown

# System Overview - v0.3.0
**Version**: 0.3.0
**Date**: 2025-12-22
**Status**: Phase 3 Design
## Introduction
This document describes the design for Phase 3 of Sneaky Klaus, building on the participant authentication foundation established in v0.2.0. This phase focuses on participant self-management capabilities and pre-matching participant experience.
**Phase 3 Scope**: Implement Epic 6 (Participant Self-Management) and Epic 4.5 (View Participant List Pre-Matching), enabling participants to manage their own profiles and see who else has registered before matching occurs.
## Phase Goals
The primary goals for v0.3.0 are:
1. **Enable participant self-service**: Allow participants to update their profiles without admin intervention
2. **Support graceful withdrawals**: Handle participants who need to drop out before matching
3. **Build social engagement**: Let participants see who else is participating (pre-matching only)
4. **Maintain data integrity**: Ensure profile changes and withdrawals don't break the exchange flow
## User Stories in Scope
### Epic 6: Participant Self-Management
- **6.1 Update Profile**: Participants can edit their name and gift ideas before matching
- **6.2 Withdraw from Exchange**: Participants can opt out before registration closes
- **6.3 Update Reminder Preferences**: Participants can toggle reminder emails on/off
### Epic 4: Participant Registration (Continuation)
- **4.5 View Participant List (Pre-Matching)**: Registered participants can see who else has joined
## Out of Scope for v0.3.0
The following features are explicitly **not** included in this phase:
- Post-matching participant experience (Epic 11) - deferred to Phase 4
- Matching system (Epic 8) - deferred to Phase 5
- Admin exchange management beyond what v0.1.0 provided
- Participant removal by admin (Epic 9) - deferred to Phase 6
- Notification emails beyond registration confirmation (Epic 10) - partial in Phase 2, remainder in Phase 7
## Architecture Overview
Phase 3 builds incrementally on the v0.2.0 architecture with no new infrastructure components:
```mermaid
graph TB
subgraph "Phase 3 Additions"
ProfileUpdate[Profile Update UI]
WithdrawUI[Withdraw UI]
ParticipantList[Participant List View]
ProfileUpdate --> ParticipantRoutes
WithdrawUI --> ParticipantRoutes
ParticipantList --> ParticipantRoutes
end
subgraph "Existing v0.2.0 Foundation"
ParticipantRoutes[Participant Routes]
ParticipantAuth[Participant Auth Decorator]
ParticipantModel[Participant Model]
ExchangeModel[Exchange Model]
Database[(SQLite)]
ParticipantRoutes --> ParticipantAuth
ParticipantRoutes --> ParticipantModel
ParticipantRoutes --> ExchangeModel
ParticipantModel --> Database
ExchangeModel --> Database
end
```
## Technical Design Principles
This phase adheres to the project's core principles established in v0.1.0 and v0.2.0:
1. **No new database tables**: All features use existing Participant and Exchange models
2. **No new external dependencies**: Pure Flask/SQLAlchemy implementation
3. **Soft deletes**: Withdrawals use `withdrawn_at` timestamp rather than hard deletes
4. **State-based permissions**: Operations restricted based on exchange state
5. **TDD approach**: Write tests first, implement to pass
## Key Design Decisions
### 1. Profile Update Restrictions
**Decision**: Profile updates are only allowed when exchange is in `draft`, `registration_open`, or `registration_closed` states. After matching, profiles are locked.
**Rationale**:
- Gift ideas are sent to givers during match notification - changes after matching would create inconsistency
- Name changes after matching could confuse participants
- Clear state transition prevents data inconsistency
**Implementation**:
```python
def can_update_profile(participant: Participant) -> bool:
"""Check if participant can update their profile."""
exchange = participant.exchange
return exchange.state in ['draft', 'registration_open', 'registration_closed']
```
### 2. Withdrawal Rules
**Decision**: Withdrawals are only allowed before registration closes. After registration closes, admin intervention required.
**Rationale**:
- Pre-closure: minimal impact, just removes one participant
- Post-closure: admin likely already configuring exclusions or has matched
- Post-matching: requires re-match, admin should make this decision
- Clear deadline prevents last-minute dropouts
**Implementation**:
```python
def can_withdraw(participant: Participant) -> bool:
"""Check if participant can withdraw themselves."""
exchange = participant.exchange
return exchange.state in ['draft', 'registration_open']
```
### 3. Withdrawal Implementation (Soft Delete)
**Decision**: Use existing `withdrawn_at` timestamp field, don't delete participant records.
**Rationale**:
- Data model already supports soft deletes (v0.2.0 design)
- Preserves audit trail of who registered
- Prevents re-use of email during same exchange
- Allows admin to see withdrawal history
- Simplifies database constraints (no cascade delete issues)
**Implementation**:
```python
def withdraw_participant(participant: Participant):
"""Mark participant as withdrawn."""
participant.withdrawn_at = datetime.utcnow()
db.session.commit()
```
### 4. Participant List Visibility
**Decision**: Show participant list to all registered participants, but only show names (not emails or gift ideas).
**Rationale**:
- Social aspect: participants want to know who's participating
- Privacy: emails are admin-only
- Security: gift ideas are for givers only (post-matching)
- Pre-matching only: post-matching handled in Phase 4
**Display Rules**:
- Show: participant names, count
- Hide: emails, gift ideas, withdrawn participants
- Filter: exclude withdrawn participants from list
### 5. Reminder Preference Changes
**Decision**: Allow reminder preference changes at any time before exchange completes.
**Rationale**:
- Low-impact change (doesn't affect matching or other participants)
- User preference should be flexible
- No technical reason to restrict after matching
- Allows opting out if circumstances change
**Implementation**: Simple boolean toggle, no state restrictions.
### 6. Form Validation Strategy
**Decision**: Use server-side validation with WTForms, add client-side hints for UX.
**Rationale**:
- Consistent with Phase 2 implementation decisions
- Security: never trust client-side validation
- UX: client-side provides immediate feedback
- Progressive enhancement: works without JavaScript
## Data Flow
### Profile Update Flow
```mermaid
sequenceDiagram
participant P as Participant Browser
participant F as Flask App
participant DB as SQLite Database
P->>F: GET /participant/profile/edit
F->>F: Check @participant_required
F->>DB: Load participant & exchange
F->>F: Check can_update_profile()
F-->>P: Render edit form (or error)
P->>F: POST /participant/profile/edit (name, gift_ideas)
F->>F: Validate form
F->>F: Check can_update_profile()
F->>DB: Update participant record
DB-->>F: Success
F-->>P: Redirect to dashboard with success message
```
### Withdrawal Flow
```mermaid
sequenceDiagram
participant P as Participant Browser
participant F as Flask App
participant DB as SQLite Database
participant E as Email Service
P->>F: POST /participant/withdraw (with confirmation)
F->>F: Check @participant_required
F->>DB: Load participant & exchange
F->>F: Check can_withdraw()
F->>DB: Set withdrawn_at timestamp
DB-->>F: Success
F->>E: Send withdrawal confirmation email
E-->>F: Email sent
F->>F: Clear session (log out participant)
F-->>P: Redirect to public page with confirmation
```
### Participant List Flow
```mermaid
sequenceDiagram
participant P as Participant Browser
participant F as Flask App
participant DB as SQLite Database
P->>F: GET /participant/dashboard
F->>F: Check @participant_required
F->>DB: Load participant's exchange
F->>DB: Query active participants (withdrawn_at IS NULL)
DB-->>F: Participant list (names only)
F-->>P: Render dashboard with participant list
```
## State Machine Impact
Phase 3 doesn't add new exchange states, but adds participant-level state:
```mermaid
stateDiagram-v2
[*] --> Registered: Registration
Registered --> UpdatedProfile: Edit profile
UpdatedProfile --> UpdatedProfile: Edit again
UpdatedProfile --> Withdrawn: Withdraw
Registered --> Withdrawn: Withdraw
UpdatedProfile --> Matched: Admin matches
Registered --> Matched: Admin matches
Withdrawn --> [*]
note right of Withdrawn
Soft delete: withdrawn_at set
Cannot re-activate
Must re-register with new email
end note
note right of UpdatedProfile
Only before matching
Name and gift_ideas editable
end note
```
## Component Design
### Routes (participant_bp)
New routes added to existing `src/routes/participant.py`:
| Route | Method | Auth | Description |
|-------|--------|------|-------------|
| `/participant/profile/edit` | GET, POST | @participant_required | Edit profile (name, gift ideas) |
| `/participant/preferences` | POST | @participant_required | Update reminder preference |
| `/participant/withdraw` | POST | @participant_required | Withdraw from exchange |
Existing routes used:
- `/participant/dashboard` - enhanced to show participant list
### Forms (src/forms/participant.py)
New forms:
**ProfileUpdateForm**:
```python
class ProfileUpdateForm(FlaskForm):
name = StringField('Name', validators=[DataRequired(), Length(max=255)])
gift_ideas = TextAreaField('Gift Ideas', validators=[Length(max=10000)])
submit = SubmitField('Save Changes')
```
**ReminderPreferenceForm**:
```python
class ReminderPreferenceForm(FlaskForm):
reminder_enabled = BooleanField('Send me reminders')
submit = SubmitField('Update Preferences')
```
**WithdrawForm**:
```python
class WithdrawForm(FlaskForm):
confirm = BooleanField('I understand this cannot be undone',
validators=[DataRequired()])
submit = SubmitField('Withdraw from Exchange')
```
### Templates
New templates in `templates/participant/`:
- `profile_edit.html` - Profile update form
- `withdraw.html` - Withdrawal confirmation page (with warnings)
- `participant_list.html` - Reusable component for displaying participant names
Enhanced templates:
- `dashboard.html` - Add participant list section
### Email Templates
New email template in `templates/emails/participant/`:
- `withdrawal_confirmation.html` - Sent when participant withdraws
- `withdrawal_confirmation.txt` - Plain text version
## Security Considerations
### 1. Authorization Checks
Every participant operation must verify:
- User is authenticated as participant (@participant_required)
- User owns the resource (participant_id matches session)
- Exchange state allows the operation (can_update_profile, can_withdraw)
### 2. CSRF Protection
All POST operations require CSRF tokens (WTForms automatic).
### 3. Input Validation
- Name: 1-255 characters, required
- Gift ideas: 0-10,000 characters, optional
- All inputs sanitized via Jinja2 auto-escaping
### 4. Privacy
Participant list shows:
- Names only (public within exchange)
- Not emails (admin-only)
- Not gift ideas (giver-only post-matching)
- Not withdrawn participants (respect withdrawal privacy)
### 5. Rate Limiting
No new rate limiting needed:
- Profile updates: legitimate user operation, no abuse vector
- Withdrawals: self-limiting (can only withdraw once)
- Participant list: read-only, authenticated
## Testing Strategy
### Unit Tests
Test business logic in isolation:
- `can_update_profile()` logic for each exchange state
- `can_withdraw()` logic for each exchange state
- Soft delete implementation (withdrawn_at timestamp)
- Participant list filtering (exclude withdrawn)
### Integration Tests
Test full request/response cycles:
- Profile update happy path
- Profile update when locked (post-matching)
- Withdrawal happy path
- Withdrawal when not allowed
- Participant list rendering
### Test Data Setup
Reuse existing test fixtures from Phase 2, add:
- Multiple participants in same exchange
- Participants in different states (registered, withdrawn)
- Exchanges in different states
### Coverage Target
Maintain 80%+ coverage established in previous phases.
## Error Handling
### Expected Errors
| Scenario | HTTP Status | User Message | Action |
|----------|-------------|--------------|--------|
| Update profile after matching | 400 | "Your profile is locked after matching" | Redirect to dashboard |
| Withdraw after registration closes | 400 | "Withdrawal deadline has passed. Contact admin." | Redirect to dashboard |
| Invalid form data | 400 | Field-specific errors | Re-render form with errors |
| Already withdrawn | 400 | "You have already withdrawn" | Redirect to public page |
### Unexpected Errors
- Database errors: Log, flash generic error, redirect safely
- Missing participant record: Clear session, redirect to landing page
- Orphaned session: Clear session, redirect to landing page
## Performance Considerations
### Database Queries
**Participant List Query**:
```python
# Efficient query with single DB hit
participants = Participant.query.filter(
Participant.exchange_id == exchange_id,
Participant.withdrawn_at.is_(None)
).order_by(Participant.name).all()
```
**Profile Load**:
- Already loaded by @participant_required decorator (uses `g.participant`)
- No additional query needed
### Caching
Not needed for v0.3.0:
- Participant counts are small (3-100 typical)
- Updates are infrequent
- Reads are authenticated (no public caching)
## Migration Requirements
**No database migrations required for v0.3.0.**
All fields already exist:
- `participant.name` - editable
- `participant.gift_ideas` - editable
- `participant.reminder_enabled` - editable
- `participant.withdrawn_at` - set on withdrawal
## Deployment Impact
### Breaking Changes
None. v0.3.0 is fully backward compatible with v0.2.0.
### Configuration Changes
None. No new environment variables or configuration needed.
### Data Migration
None. Existing data fully compatible.
## User Experience Flow
### Participant Journey (Pre-Matching)
1. **Register** (Phase 2)
- Receive confirmation email with magic link
2. **View Dashboard** (Phase 2 + Phase 3)
- See own information
- **NEW**: See list of other participants (names only)
3. **Update Profile** (Phase 3)
- Click "Edit Profile" from dashboard
- Update name or gift ideas
- Save changes
- See confirmation message
4. **Change Reminder Preference** (Phase 3)
- Toggle reminder checkbox on dashboard
- See confirmation message
5. **Withdraw (if needed)** (Phase 3)
- Click "Withdraw" from dashboard
- See warning about permanence
- Confirm withdrawal
- Receive confirmation email
- Logged out and redirected to public page
## Admin Experience Impact
Phase 3 adds visibility for admins:
### Exchange Detail View (Enhancement)
Add to existing exchange detail page:
- Show participant list with withdrawn indicator
- Display: "5 active, 1 withdrawn"
- Allow admin to see withdrawn participants (grayed out)
**Implementation Note**: This is a minor enhancement to existing admin routes, not a major feature. Can be implemented alongside participant features or deferred to Phase 6 (Admin Participant Management).
## Future Considerations
### Phase 4 Dependencies
Phase 3 sets up foundations for Phase 4 (Post-Matching Experience):
- Participant list already implemented (will be reused post-matching)
- Profile lock logic (can_update_profile) prevents changes after matching
- Dashboard structure ready to show match assignment
### Potential Enhancements (Out of Scope)
Not included in v0.3.0 but possible future additions:
- Email notification to admin when participant withdraws (Epic 10.5)
- Allow participants to indicate dietary restrictions or allergies
- Allow participants to add profile pictures
- Allow participants to message admin (anonymous contact form)
## Acceptance Criteria
Phase 3 is complete when:
1. ✅ Participants can update their name and gift ideas before matching
2. ✅ Participants cannot update profile after matching occurs
3. ✅ Participants can withdraw before registration closes
4. ✅ Participants cannot withdraw after registration closes
5. ✅ Withdrawn participants receive confirmation email
6. ✅ Withdrawn participants are logged out and removed from participant list
7. ✅ Participants can view list of other registered participants (names only)
8. ✅ Participant list excludes withdrawn participants
9. ✅ Participants can toggle reminder preferences at any time
10. ✅ All operations require participant authentication
11. ✅ All operations validate exchange state appropriately
12. ✅ All user stories have passing integration tests
13. ✅ Code coverage remains at 80%+
## Implementation Phases
Recommended implementation order (TDD, vertical slices):
### Phase 3.1: Participant List View
- Story 4.5: View Participant List (Pre-Matching)
- Simplest feature, no state changes
- Sets up dashboard enhancements
### Phase 3.2: Profile Updates
- Story 6.1: Update Profile
- Core self-management feature
- Tests state-based permissions
### Phase 3.3: Reminder Preferences
- Story 6.3: Update Reminder Preferences
- Simple toggle, low risk
- Quick win
### Phase 3.4: Withdrawal
- Story 6.2: Withdraw from Exchange
- Most complex (state changes, email, logout)
- Benefits from previous features being solid
## References
- [v0.2.0 System Overview](../v0.2.0/overview.md)
- [v0.2.0 Data Model](../v0.2.0/data-model.md)
- [Product Backlog](../../BACKLOG.md)
- [Project Overview](../../PROJECT_OVERVIEW.md)
- [ADR-0002: Authentication Strategy](../../decisions/0002-authentication-strategy.md)
- [ADR-0003: Participant Session Scoping](../../decisions/0003-participant-session-scoping.md)
---
**End of Phase 3 Design Overview**