Allows guests to cancel their RSVP via a DELETE endpoint using their guestToken. Frontend shows cancel button in RsvpBar and clears local storage on success. Includes unit tests, integration tests, and E2E spec. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
12 KiB
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.
- T001 Add DELETE
/events/{token}/rsvps/{rsvpToken}endpoint tobackend/src/main/resources/openapi/api.yamlperspecs/014-cancel-rsvp/contracts/cancel-rsvp.yaml— operationIdcancelRsvp, responses 204 (success/idempotent) and 500 - T002 Regenerate backend API interfaces from updated OpenAPI spec via
cd backend && ./mvnw generate-sources - 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)
- T004 [P] Write unit test for
cancelRsvp()inbackend/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) - T005 [P] Write integration test for
DELETE /api/events/{token}/rsvps/{rsvpToken}inbackend/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
- T006 Create
CancelRsvpUseCaseinterface inbackend/src/main/java/de/fete/domain/port/in/CancelRsvpUseCase.java— single methodcancelRsvp(EventToken, RsvpToken)returning void - T007 Add
deleteByEventIdAndRsvpToken(Long eventId, RsvpToken rsvpToken)to domain portbackend/src/main/java/de/fete/domain/port/out/RsvpRepository.java - T008 Add
deleteByEventIdAndRsvpToken(Long eventId, UUID rsvpToken)derived delete query tobackend/src/main/java/de/fete/adapter/out/persistence/RsvpJpaRepository.java - T009 Implement
deleteByEventIdAndRsvpToken()inbackend/src/main/java/de/fete/adapter/out/persistence/RsvpPersistenceAdapter.java— delegate to JPA repository, return boolean (deleted count > 0) - T010 Implement
CancelRsvpUseCaseinbackend/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 - T011 Implement
cancelRsvp()handler inbackend/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 - 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)
- 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
- T014 [US1] Add
removeRsvp(eventToken: string)method tofrontend/src/composables/useEventStorage.ts— clearsrsvpTokenandrsvpNamefrom the stored event without removing the event from the list - T015 [US1] Modify
frontend/src/components/RsvpBar.vue— make status state tappable with tap-to-reveal pattern: (1) addexpandedstate, (2) tapping "You're attending!" toggles expanded, (3) expanded state shows "Cancel attendance" button, (4) emitcancelevent 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) - T016 [US1] Add cancel RSVP logic to
frontend/src/views/EventDetailView.vue— (1) handlecancelemit from RsvpBar, (2) show ConfirmDialog with message "Your attendance will be permanently cancelled.", (3) on confirm: callapi.DELETE('/events/{token}/rsvps/{rsvpToken}'), (4) on 204: callremoveRsvp(), 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 - T017 [US1] Run frontend unit tests
cd frontend && npm run test:unitand E2E testscd 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)
- 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
- T019 [US2] Modify
frontend/src/components/EventList.vue— (1) detect if pending-delete event has RSVP token viagetRsvp(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) makeconfirmDelete()async: if RSVP exists, callapi.DELETE('/events/{token}/rsvps/{rsvpToken}')first, (4) on success or 404: proceed withremoveEvent(), (5) on other error: show error message, abort removal - T020 [US2] Import API client and
getRsvpfromuseEventStorageinfrontend/src/components/EventList.vue— ensure API client is available for server calls - 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)
- 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
- 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 - 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 withremoveEvent()(note: this may already be handled if T019 implemented "success or 404" correctly — verify and adjust if needed) - 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.
- T026 Run full backend verify
cd backend && ./mvnw verify— checkstyle + all tests - T027 Run full frontend test suite
cd frontend && npm run test:unit && npx playwright test— all unit + E2E tests - 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)
# 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
# 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)
- Complete Phase 1: Setup (OpenAPI + type generation)
- Complete Phase 2: Foundational (backend DELETE endpoint)
- Complete Phase 3: User Story 1 (cancel from detail view)
- STOP and VALIDATE: Test cancel flow end-to-end
- Deploy/demo if ready
Incremental Delivery
- Setup + Foundational → Backend ready
- Add US1 → Cancel from detail view works → Deploy (MVP!)
- Add US2 → Auto-cancel on list removal → Deploy
- Add US3 → Stale token edge cases handled → Deploy
- 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
@Transactionalannotation is required on the service method calling JPAdeleteBy...