# Tasks: Cancel RSVP **Input**: Design documents from `/specs/014-cancel-rsvp/` **Prerequisites**: plan.md (required), spec.md (required), research.md, data-model.md, contracts/ **Tests**: Included — constitution mandates TDD (Red → Green → Refactor). E2E tests mandatory per principle II. **Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story. ## Format: `[ID] [P?] [Story] Description` - **[P]**: Can run in parallel (different files, no dependencies) - **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3) - Include exact file paths in descriptions --- ## Phase 1: Setup (API Contract & Type Generation) **Purpose**: Define the DELETE endpoint contract and regenerate types for both backend and frontend. - [x] T001 Add DELETE `/events/{token}/rsvps/{rsvpToken}` endpoint to `backend/src/main/resources/openapi/api.yaml` per `specs/014-cancel-rsvp/contracts/cancel-rsvp.yaml` — operationId `cancelRsvp`, responses 204 (success/idempotent) and 500 - [x] T002 Regenerate backend API interfaces from updated OpenAPI spec via `cd backend && ./mvnw generate-sources` - [x] T003 Regenerate frontend TypeScript types from updated OpenAPI spec via `cd frontend && npm run generate:api` --- ## Phase 2: Foundational (Backend Delete Infrastructure) **Purpose**: Backend repository and service layer for RSVP deletion — blocks all user stories. **⚠️ CRITICAL**: No user story work can begin until this phase is complete. ### Tests (write FIRST, must FAIL before implementation) - [x] T004 [P] Write unit test for `cancelRsvp()` in `backend/src/test/java/de/fete/application/service/RsvpServiceTest.java` — test cases: successful delete (returns true), RSVP not found (returns false), event not found (returns false) - [x] T005 [P] Write integration test for `DELETE /api/events/{token}/rsvps/{rsvpToken}` in `backend/src/test/java/de/fete/adapter/in/web/EventControllerIntegrationTest.java` — test cases: 204 on successful delete, 204 on already-deleted RSVP (idempotent), 204 when event not found (idempotent), verify RSVP row actually removed from DB ### Implementation - [x] T006 Create `CancelRsvpUseCase` interface in `backend/src/main/java/de/fete/domain/port/in/CancelRsvpUseCase.java` — single method `cancelRsvp(EventToken, RsvpToken)` returning void - [x] T007 Add `deleteByEventIdAndRsvpToken(Long eventId, RsvpToken rsvpToken)` to domain port `backend/src/main/java/de/fete/domain/port/out/RsvpRepository.java` - [x] T008 Add `deleteByEventIdAndRsvpToken(Long eventId, UUID rsvpToken)` derived delete query to `backend/src/main/java/de/fete/adapter/out/persistence/RsvpJpaRepository.java` - [x] T009 Implement `deleteByEventIdAndRsvpToken()` in `backend/src/main/java/de/fete/adapter/out/persistence/RsvpPersistenceAdapter.java` — delegate to JPA repository, return boolean (deleted count > 0) - [x] T010 Implement `CancelRsvpUseCase` in `backend/src/main/java/de/fete/application/service/RsvpService.java` — look up event by token, if found call repository delete, no error on not-found (idempotent). Add `@Transactional` - [x] T011 Implement `cancelRsvp()` handler in `backend/src/main/java/de/fete/adapter/in/web/EventController.java` — accept event token and RSVP token path params, call use case, return 204 No Content - [x] T012 Run `cd backend && ./mvnw verify` — all tests (existing + new) must pass **Checkpoint**: Backend DELETE endpoint functional and tested. Verify via integration tests. --- ## Phase 3: User Story 1 — Cancel RSVP from Event Detail View (Priority: P1) 🎯 MVP **Goal**: Guest can tap the "You're attending!" bar to reveal a cancel button, confirm cancellation, and have RSVP deleted server-side with UI reset. **Independent Test**: Navigate to event detail as RSVP'd guest → tap status bar → tap cancel → confirm → verify RSVP deleted, attendee count decremented, bar reset to CTA state. Then re-RSVP to verify flow works again. ### Tests (write FIRST, must FAIL before implementation) - [x] T013 [US1] Write E2E test file `frontend/e2e/cancel-rsvp.spec.ts` — US1 scenarios: (1) status bar shows cancel affordance when RSVP'd, (2) tap reveals cancel button, (3) confirm cancellation → 204 → localStorage cleared + count decremented + bar reset, (4) server error → error message + state unchanged, (5) re-RSVP after cancel works ### Implementation - [x] T014 [US1] Add `removeRsvp(eventToken: string)` method to `frontend/src/composables/useEventStorage.ts` — clears `rsvpToken` and `rsvpName` from the stored event without removing the event from the list - [x] T015 [US1] Modify `frontend/src/components/RsvpBar.vue` — make status state tappable with tap-to-reveal pattern: (1) add `expanded` state, (2) tapping "You're attending!" toggles expanded, (3) expanded state shows "Cancel attendance" button, (4) emit `cancel` event on button click, (5) collapse on outside click/Escape, (6) add subtle chevron icon hint, (7) ARIA: `role="button"`, `aria-expanded`, keyboard support (Enter/Space to toggle, Escape to collapse) - [x] T016 [US1] Add cancel RSVP logic to `frontend/src/views/EventDetailView.vue` — (1) handle `cancel` emit from RsvpBar, (2) show ConfirmDialog with message "Your attendance will be permanently cancelled.", (3) on confirm: call `api.DELETE('/events/{token}/rsvps/{rsvpToken}')`, (4) on 204: call `removeRsvp()`, decrement attendee count, reset RSVP state (`rsvpName = ''`), (5) on error: show error message "Could not cancel RSVP. Please try again.", (6) keep local state unchanged on error - [x] T017 [US1] Run frontend unit tests `cd frontend && npm run test:unit` and E2E tests `cd frontend && npx playwright test cancel-rsvp` — all must pass **Checkpoint**: US1 fully functional. Guest can cancel RSVP from event detail view and re-RSVP. --- ## Phase 4: User Story 2 — Auto-Cancel on Event List Removal (Priority: P2) **Goal**: When a guest removes an RSVP'd event from their event list, the confirmation dialog warns about attendance cancellation and the RSVP is deleted server-side before localStorage cleanup. **Independent Test**: Add RSVP'd event to list → initiate removal → verify dialog mentions attendance → confirm → verify server DELETE called → event removed from list. Also test: event without RSVP shows standard dialog (no mention of attendance). ### Tests (write FIRST, must FAIL before implementation) - [x] T018 [US2] Add US2 E2E scenarios to `frontend/e2e/cancel-rsvp.spec.ts` — (1) removal of RSVP'd event shows "attendance will be cancelled" in dialog, (2) removal of non-RSVP'd event shows standard dialog (no attendance mention), (3) confirm removal → DELETE called → event removed from list, (4) server error on DELETE → error message + event stays in list, (5) dismiss dialog → no changes ### Implementation - [x] T019 [US2] Modify `frontend/src/components/EventList.vue` — (1) detect if pending-delete event has RSVP token via `getRsvp(eventToken)`, (2) set conditional dialog message: with RSVP → "This event will be removed from your list and your attendance will be cancelled." / without RSVP → existing message, (3) make `confirmDelete()` async: if RSVP exists, call `api.DELETE('/events/{token}/rsvps/{rsvpToken}')` first, (4) on success or 404: proceed with `removeEvent()`, (5) on other error: show error message, abort removal - [x] T020 [US2] Import API client and `getRsvp` from `useEventStorage` in `frontend/src/components/EventList.vue` — ensure API client is available for server calls - [x] T021 [US2] Run E2E tests `cd frontend && npx playwright test cancel-rsvp` — all US1 + US2 scenarios must pass **Checkpoint**: US1 + US2 functional. Both cancel paths work independently. --- ## Phase 5: User Story 3 — Cancel RSVP with Expired/Invalid Token (Priority: P3) **Goal**: Gracefully handle stale RSVP tokens — treat "not found" server responses as successful cancellation. **Independent Test**: Set stale RSVP token in localStorage → attempt cancel from detail view → verify 404 treated as success → localStorage cleaned. Same for event list removal. ### Tests (write FIRST, must FAIL before implementation) - [x] T022 [US3] Add US3 E2E scenarios to `frontend/e2e/cancel-rsvp.spec.ts` — (1) cancel from detail view with stale token (server 404) → treated as success, localStorage cleaned, UI reset, (2) event list removal with stale token (server 404) → treated as success, event removed ### Implementation - [x] T023 (already implemented in T016 — 404 treated as success) [US3] Update cancel logic in `frontend/src/views/EventDetailView.vue` — treat 404 response from DELETE as success (RSVP already gone): clean up localStorage and reset UI same as 204 - [x] T024 (already implemented in T019 — 404 treated as success) [US3] Update cancel logic in `frontend/src/components/EventList.vue` — treat 404 response from DELETE as success: proceed with `removeEvent()` (note: this may already be handled if T019 implemented "success or 404" correctly — verify and adjust if needed) - [x] T025 [US3] Run all E2E tests `cd frontend && npx playwright test cancel-rsvp` — all US1 + US2 + US3 scenarios must pass **Checkpoint**: All user stories functional. Edge cases handled gracefully. --- ## Phase 6: Polish & Cross-Cutting Concerns **Purpose**: Final verification and cleanup across all stories. - [x] T026 Run full backend verify `cd backend && ./mvnw verify` — checkstyle + all tests - [x] T027 Run full frontend test suite `cd frontend && npm run test:unit && npx playwright test` — all unit + E2E tests - [x] T028 Verify accessibility: RsvpBar cancel interaction is keyboard-navigable (Tab, Enter/Space, Escape), ARIA attributes correct, confirm dialog focus management works --- ## Dependencies & Execution Order ### Phase Dependencies - **Setup (Phase 1)**: No dependencies — start immediately - **Foundational (Phase 2)**: Depends on Phase 1 (generated API interfaces needed) - **US1 (Phase 3)**: Depends on Phase 2 (backend endpoint must exist) - **US2 (Phase 4)**: Depends on Phase 2 (backend endpoint must exist). Independent of US1. - **US3 (Phase 5)**: Depends on US1 and US2 (refines their error handling) - **Polish (Phase 6)**: Depends on all stories complete ### User Story Dependencies - **US1 (P1)**: Independent after Phase 2 — core cancel flow - **US2 (P2)**: Independent after Phase 2 — can be implemented in parallel with US1 - **US3 (P3)**: Depends on US1 + US2 — refines error handling in both ### Within Each User Story - Tests MUST be written and FAIL before implementation - Composable/model changes before component changes - Component changes before view integration - Story complete before moving to next priority ### Parallel Opportunities - T004 + T005 (backend tests) can run in parallel - T006 + T007 (use case + repository port) can run in parallel - US1 and US2 can be implemented in parallel after Phase 2 (different files) - T026 + T027 (backend verify + frontend tests) can run in parallel --- ## Parallel Example: Phase 2 (Foundational) ```bash # Write tests in parallel: Task T004: "Unit test for cancelRsvp in RsvpServiceTest.java" Task T005: "Integration test for DELETE endpoint in EventControllerIntegrationTest.java" # Then implement sequentially: T006 → T007 → T008 → T009 → T010 → T011 → T012 ``` ## Parallel Example: US1 + US2 ```bash # After Phase 2 completes, launch in parallel: # Stream A (US1): T013 → T014 → T015 → T016 → T017 # Stream B (US2): T018 → T019 → T020 → T021 ``` --- ## Implementation Strategy ### MVP First (User Story 1 Only) 1. Complete Phase 1: Setup (OpenAPI + type generation) 2. Complete Phase 2: Foundational (backend DELETE endpoint) 3. Complete Phase 3: User Story 1 (cancel from detail view) 4. **STOP and VALIDATE**: Test cancel flow end-to-end 5. Deploy/demo if ready ### Incremental Delivery 1. Setup + Foundational → Backend ready 2. Add US1 → Cancel from detail view works → Deploy (MVP!) 3. Add US2 → Auto-cancel on list removal → Deploy 4. Add US3 → Stale token edge cases handled → Deploy 5. Each story adds value without breaking previous stories --- ## Notes - [P] tasks = different files, no dependencies - [Story] label maps task to specific user story for traceability - Each user story should be independently completable and testable - Verify tests fail before implementing - Commit after each task or logical group - Stop at any checkpoint to validate story independently - Backend idempotent DELETE (204 always) simplifies all frontend error handling - The `@Transactional` annotation is required on the service method calling JPA `deleteBy...`