# 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**