# Implementation Plan: Cancel RSVP

**Branch**: `014-cancel-rsvp` | **Date**: 2026-03-09 | **Spec**: [spec.md](spec.md)
**Input**: Feature specification from `/specs/014-cancel-rsvp/spec.md`

## Summary

Allow guests to cancel their RSVP either explicitly from the event detail view (tap-to-reveal pattern on the RSVP bar) or implicitly when removing an RSVP'd event from their event list. The backend provides an idempotent DELETE endpoint; the frontend handles confirmation, API call, localStorage cleanup, and UI state reset.

## Technical Context

**Language/Version**: Java 25 (backend), TypeScript 5.9 (frontend)
**Primary Dependencies**: Spring Boot 3.5.x, Vue 3, Vue Router 5, openapi-fetch, openapi-typescript
**Storage**: PostgreSQL (JPA via Spring Data, Liquibase migrations)
**Testing**: JUnit + Mockito (backend unit), MockMvc + Testcontainers (backend integration), Vitest (frontend unit), Playwright + MSW (frontend E2E)
**Target Platform**: Self-hosted PWA (web browser, mobile-first)
**Project Type**: Web application (hexagonal backend + SPA frontend)
**Performance Goals**: Cancel operation < 500ms server-side
**Constraints**: Privacy by design (no analytics), token-based auth (no login), idempotent delete
**Scale/Scope**: Single new endpoint, 3 modified frontend components, 1 new composable method

## Constitution Check

*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*

| Principle | Status | Notes |
|-----------|--------|-------|
| I. Privacy by Design | PASS | No new PII stored. Delete operation removes data. No analytics. |
| II. Test-Driven Methodology | PASS | Plan includes TDD cycle: tests before implementation. E2E mandatory. |
| III. API-First Development | PASS | OpenAPI spec updated first, types generated before implementation. |
| IV. Simplicity & Quality | PASS | Minimal changes to existing code. No new abstractions. Idempotent delete is the simplest correct approach. |
| V. Dependency Discipline | PASS | No new dependencies required. |
| VI. Accessibility | PASS | Interactive elements will use semantic HTML, ARIA, keyboard navigation. Confirm dialog already accessible. |

**Gate result**: ALL PASS — proceed to Phase 0.

## Project Structure

### Documentation (this feature)

```text
specs/014-cancel-rsvp/
├── plan.md              # This file
├── research.md          # Phase 0 output
├── data-model.md        # Phase 1 output
├── contracts/           # Phase 1 output
│   └── cancel-rsvp.yaml # DELETE endpoint contract
└── tasks.md             # Phase 2 output (by /speckit.tasks)
```

### Source Code (repository root)

```text
backend/
├── src/main/java/de/fete/
│   ├── domain/
│   │   ├── model/Rsvp.java                          # existing (no changes)
│   │   ├── model/RsvpToken.java                     # existing (no changes)
│   │   └── port/
│   │       ├── in/CancelRsvpUseCase.java            # NEW use case port
│   │       └── out/RsvpRepository.java              # MODIFY (add deleteByRsvpToken)
│   ├── application/service/RsvpService.java          # MODIFY (implement cancel)
│   └── adapter/
│       ├── in/web/EventController.java               # MODIFY (add DELETE handler)
│       └── out/persistence/
│           ├── RsvpJpaRepository.java                # MODIFY (add deleteByRsvpToken)
│           └── RsvpPersistenceAdapter.java           # MODIFY (implement delete)
├── src/main/resources/openapi/api.yaml               # MODIFY (add DELETE endpoint)
└── src/test/java/de/fete/
    ├── application/service/RsvpServiceTest.java      # MODIFY (add cancel tests)
    └── adapter/in/web/EventControllerIntegrationTest.java  # MODIFY (add cancel tests)

frontend/
├── src/
│   ├── api/schema.d.ts                               # REGENERATE (from updated OpenAPI)
│   ├── components/
│   │   ├── RsvpBar.vue                               # MODIFY (tap-to-reveal cancel)
│   │   ├── EventList.vue                             # MODIFY (conditional dialog msg)
│   │   └── ConfirmDialog.vue                         # existing (no changes)
│   ├── composables/useEventStorage.ts                # MODIFY (add removeRsvp)
│   └── views/EventDetailView.vue                     # MODIFY (add cancel logic)
└── e2e/
    └── cancel-rsvp.spec.ts                           # NEW (E2E tests)
```

**Structure Decision**: Follows existing hexagonal architecture. New use case port + implementation in existing service. Single new endpoint added to existing controller.

## Complexity Tracking

No constitution violations — table not needed.
