# US-1 Post-Review Fixes — Implementation Plan Date: 2026-03-05 Origin: Deep review of all unstaged US-1 changes before commit ## Context US-1 "Create Event" is fully implemented (backend + frontend, 7 phases) with 4 review fixes already applied (reactive error clearing, network error handling, page title, favicon). A comprehensive review of ALL unstaged files revealed additional issues that must be fixed before committing. ## Task 1: Backend — Clock injection in EventService [x] **Problem:** `EventService` uses `LocalDate.now()` and `OffsetDateTime.now()` directly, making deterministic time-based testing impossible. **Files:** - `backend/src/main/java/de/fete/application/service/EventService.java` - `backend/src/test/java/de/fete/application/service/EventServiceTest.java` **Fix:** 1. Inject a `java.time.Clock` bean into `EventService` via constructor 2. Replace `LocalDate.now()` with `LocalDate.now(clock)` and `OffsetDateTime.now()` with `OffsetDateTime.now(clock)` 3. Add a `Clock` bean to the Spring config (or rely on a `@Bean Clock clock() { return Clock.systemDefaultZone(); }` in a config class) 4. Update `EventServiceTest` to use `Clock.fixed(...)` for deterministic tests **Verification:** `cd backend && ./mvnw test` ## Task 2: Frontend A11y — Error spans should only render when error present [x] **Problem:** Every form field has `{{ errors.title }}` that is always in the DOM, even when empty. Screen readers may announce empty `role="alert"` elements. **File:** `frontend/src/views/EventCreateView.vue` **Fix:** Use `v-if` to conditionally render error spans: ```html {{ errors.title }} ``` Apply to all 5 field error spans (title, description, dateTime, location, expiryDate). **Note:** This removes the `min-height: 1.2em` layout reservation. Accept the layout shift as a trade-off for accessibility, OR add a wrapper div with `min-height` that doesn't carry `role="alert"`. **Verification:** `cd frontend && npm run test:unit` — existing tests use `.querySelector('[role="alert"]')` so they may need adjustment since empty alerts will no longer be in the DOM. ## Task 3: Frontend A11y — aria-invalid and aria-describedby on fields [x] **Problem:** When a field fails validation, there is no `aria-invalid="true"` or `aria-describedby` linking the input to its error message. Assistive technologies cannot associate errors with fields. **File:** `frontend/src/views/EventCreateView.vue` **Fix:** 1. Add unique `id` to each error span (e.g., `id="title-error"`) 2. Add `:aria-describedby="errors.title ? 'title-error' : undefined"` to each input 3. Add `:aria-invalid="!!errors.title"` to each input Example for title: ```html {{ errors.title }} ``` Apply the same pattern to all 5 fields (title, description, dateTime, location, expiryDate). **Verification:** `cd frontend && npm run test:unit` ## Task 4: Frontend A11y — Error text contrast [x] **Problem:** White (`#fff`) error text on the pink gradient start (`#F06292`) has a contrast ratio of only 3.06:1, which fails WCAG AA for small text (0.8rem). The project statute requires WCAG AA compliance. **File:** `frontend/src/assets/main.css` **Fix options (pick one):** - **Option A:** Use a light yellow/cream color like `#FFF9C4` or `#FFECB3` that has higher contrast on the gradient - **Option B:** Add a subtle dark text-shadow to the error text: `text-shadow: 0 1px 2px rgba(0,0,0,0.3)` - **Option C:** Make error text slightly larger/bolder to qualify for WCAG AA-large (18px+ or 14px+ bold) **Recommended:** Option C — bump `.field-error` to `font-size: 0.85rem; font-weight: 600;` which at 600 weight qualifies for AA-large text at 14px+ (0.85rem ≈ 13.6px — close but may not quite qualify). Alternatively combine with option B for safety. **Note:** Verify the final choice against the design system spec in `spec/design-system.md`. The spec notes that gradient start only passes AA-large. The error text must work across the full gradient. **Verification:** Manual contrast check with a tool like WebAIM contrast checker. ## Task 5: Test — Happy-path submission in EventCreateView [x] **Problem:** No test verifies successful form submission (the most important behavior). **File:** `frontend/src/views/__tests__/EventCreateView.spec.ts` **Fix:** Add a test that: 1. Mocks `api.POST` to return `{ data: { eventToken: 'abc', organizerToken: 'xyz', title: 'Test', dateTime: '...', expiryDate: '...' } }` 2. Fills all required fields 3. Submits the form 4. Asserts `api.POST` was called with the correct body 5. Asserts navigation to `/events/abc` occurred 6. Asserts `saveCreatedEvent` was called (need to mock `useEventStorage`) **Note:** `useEventStorage` must be mocked. Use `vi.mock('@/composables/useEventStorage')`. **Verification:** `cd frontend && npm run test:unit` ## Task 6: Test — EventStubView component tests [x] **Problem:** No test file exists for `EventStubView.vue`. **New file:** `frontend/src/views/__tests__/EventStubView.spec.ts` **Fix:** Create tests covering: 1. Renders the event URL based on route param `:token` 2. Shows the correct share URL (`window.location.origin + /events/:token`) 3. Copy button exists 4. Back link navigates to home **Note:** Read `frontend/src/views/EventStubView.vue` first to understand the component structure. **Verification:** `cd frontend && npm run test:unit` ## Task 7: Test — Server-side field errors in EventCreateView [x] **Problem:** The `fieldErrors` handling branch (lines 184-196 of EventCreateView.vue) is untested. **File:** `frontend/src/views/__tests__/EventCreateView.spec.ts` **Fix:** Add a test that: 1. Mocks `api.POST` to return `{ error: { fieldErrors: [{ field: 'title', message: 'Title already taken' }] } }` 2. Fills all required fields and submits 3. Asserts the title field error shows "Title already taken" 4. Asserts other field errors are empty **Verification:** `cd frontend && npm run test:unit` ## Task 8: Fix border-radius on EventStubView copy button [x] **Problem:** `border-radius: 10px` is hardcoded instead of using the design token `var(--radius-button)` (14px). **File:** `frontend/src/views/EventStubView.vue` **Fix:** Replace `border-radius: 10px` with `border-radius: var(--radius-button)` in the `.stub__copy` CSS class. **Verification:** Visual check. ## Task 9: Add 404 catch-all route user story [x] **Problem:** Navigating to an unknown path shows a blank page. **File:** `spec/userstories.md` **Fix:** Add a new user story for a 404/catch-all route. Something like: ``` ### US-X: 404 Page As a user who navigates to a non-existent URL, I want to see a helpful error page so I can find my way back. Acceptance Criteria: - [ ] Unknown routes show a "Page not found" message - [ ] The page includes a link back to the home page - [ ] The page follows the design system ``` Read the existing user stories first to match the format. **Verification:** N/A (spec only). ## Task 10: EventStubView silent clipboard failure [x] **Problem:** In `EventStubView.vue`, the `catch` block on `navigator.clipboard.writeText()` is empty. If clipboard is unavailable (HTTP, older browser), the user gets no feedback. **File:** `frontend/src/views/EventStubView.vue` **Fix:** In the catch block, show a fallback message (e.g., set `copied` text to "Copy failed" or select the URL text for manual copying). **Verification:** `cd frontend && npm run test:unit` ## Execution Order 1. Task 1 (Clock injection — backend, independent) 2. Tasks 2 + 3 (A11y fixes — can be done together since they touch the same file) 3. Task 4 (Contrast fix — CSS only) 4. Tasks 5 + 7 (EventCreateView tests — same test file) 5. Task 6 (EventStubView tests — new file) 6. Tasks 8 + 10 (EventStubView fixes — same file) 7. Task 9 (User story — spec only) 8. Run all tests: `cd backend && ./mvnw test` and `cd frontend && npm run test:unit` ## Constraints - TDD: write/update tests first, then fix (where applicable) - Follow existing code style and patterns - Do not refactor unrelated code - Do not add dependencies - Update design system spec if contrast solution changes the spec