- 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>
18 KiB
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:
- Enable participant self-service: Allow participants to update their profiles without admin intervention
- Support graceful withdrawals: Handle participants who need to drop out before matching
- Build social engagement: Let participants see who else is participating (pre-matching only)
- 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:
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:
- No new database tables: All features use existing Participant and Exchange models
- No new external dependencies: Pure Flask/SQLAlchemy implementation
- Soft deletes: Withdrawals use
withdrawn_attimestamp rather than hard deletes - State-based permissions: Operations restricted based on exchange state
- 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:
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:
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:
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
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
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
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:
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:
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:
class ReminderPreferenceForm(FlaskForm):
reminder_enabled = BooleanField('Send me reminders')
submit = SubmitField('Update Preferences')
WithdrawForm:
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 formwithdraw.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 withdrawswithdrawal_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 statecan_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:
# 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- editableparticipant.gift_ideas- editableparticipant.reminder_enabled- editableparticipant.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)
-
Register (Phase 2)
- Receive confirmation email with magic link
-
View Dashboard (Phase 2 + Phase 3)
- See own information
- NEW: See list of other participants (names only)
-
Update Profile (Phase 3)
- Click "Edit Profile" from dashboard
- Update name or gift ideas
- Save changes
- See confirmation message
-
Change Reminder Preference (Phase 3)
- Toggle reminder checkbox on dashboard
- See confirmation message
-
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:
- ✅ Participants can update their name and gift ideas before matching
- ✅ Participants cannot update profile after matching occurs
- ✅ Participants can withdraw before registration closes
- ✅ Participants cannot withdraw after registration closes
- ✅ Withdrawn participants receive confirmation email
- ✅ Withdrawn participants are logged out and removed from participant list
- ✅ Participants can view list of other registered participants (names only)
- ✅ Participant list excludes withdrawn participants
- ✅ Participants can toggle reminder preferences at any time
- ✅ All operations require participant authentication
- ✅ All operations validate exchange state appropriately
- ✅ All user stories have passing integration tests
- ✅ 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 Data Model
- Product Backlog
- Project Overview
- ADR-0002: Authentication Strategy
- ADR-0003: Participant Session Scoping
End of Phase 3 Design Overview