Files
fete/specs/017-watch-event/research.md
nitrix c450849e4d
All checks were successful
CI / backend-test (push) Successful in 1m0s
CI / frontend-test (push) Successful in 27s
CI / frontend-e2e (push) Successful in 1m30s
CI / build-and-publish (push) Has been skipped
Implement watch-event feature (017) with bookmark in RsvpBar
Add client-side watch/bookmark functionality: users can save events to
localStorage without RSVPing via a bookmark button next to the "I'm attending"
CTA. Watched events appear in the event list with a "Watching" label.
Bookmark is only visible for visitors (not attendees or organizers).

Includes spec, plan, research, tasks, unit tests, and E2E tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 22:20:57 +01:00

3.9 KiB

Research: Watch Event

Feature: 017-watch-event Date: 2026-03-12

Research Questions

1. How does the current role detection work?

Finding: EventList.vue has a getRole() function that checks organizerToken first, then rsvpToken. Returns undefined when neither is present. EventCard.vue accepts an eventRole prop typed as 'organizer' | 'attendee' | undefined.

Decision: Extend getRole() to return 'watcher' when the event is in localStorage but has no organizerToken and no rsvpToken. Extend EventCard prop type to include 'watcher'.

Rationale: This is the minimal change — the existing priority chain (organizer > attendee) already handles precedence. Adding watcher as the fallback case is natural.

2. How to detect "is this event stored?" on the detail page?

Finding: useEventStorage has getStoredEvents() which returns all events, and getRsvp(eventToken) / getOrganizerToken(eventToken) for specific lookups. There is no direct isStored(eventToken) check.

Decision: Add a isStored(eventToken) method to useEventStorage that checks if an event exists in localStorage regardless of role. Add a saveWatch(eventToken, title, dateTime) method that creates a minimal StoredEvent entry (no rsvpToken, no organizerToken).

Rationale: saveWatch() is semantically distinct from saveRsvp() and saveCreatedEvent(). The isStored() helper avoids filtering through the full event list for a simple boolean check.

3. What happens to events after RSVP cancellation?

Finding: removeRsvp(eventToken) deletes rsvpToken and rsvpName but keeps the event in localStorage. After cancellation, the event has no rsvpToken and no organizerToken — identical to a watched event.

Decision: No change needed. The existing removeRsvp() behavior already produces the correct state for a "watcher" after cancellation. The getRole() update will automatically label these as "Watching".

Rationale: This is the key insight — the post-RSVP-cancellation state is already semantically equivalent to "watching". We just need to label it.

4. Bookmark icon placement and glow conflict

Finding: The event title is a plain <h1 class="detail__title">. The RsvpBar CTA uses glow-border glow-border--animated with a ::before pseudo-element that extends 12px beyond the button via inset: -4px + blur(8px). The bookmark icon is positioned at the title area (top of page), far from the RsvpBar (fixed at bottom). No glow conflict.

Decision: Place bookmark icon in a flex container with the title: display: flex; align-items: center; gap: var(--spacing-sm). Icon to the left, title takes remaining space.

Rationale: Vertically centered with flex is the simplest approach. No glow interference since the icon is nowhere near the RsvpBar.

5. Delete confirmation behavior per role

Finding: EventList.vue shows a ConfirmDialog for all deletions. The message text varies based on RSVP status. For events without RSVP, the message is generic ("This event will be removed from your list.").

Decision: Skip the confirmation dialog entirely for watchers (no rsvpToken, no organizerToken). Call removeEvent() directly on swipe/delete.

Rationale: Watching is low-commitment. The spec explicitly requires no confirmation for watcher deletion.

6. Shake animation implementation

Finding: No existing shake animation in the codebase. The RsvpBar status and cancel-event button are both position: fixed; bottom: 0.

Decision: Add a CSS @keyframes shake animation (short horizontal oscillation, ~300ms). Apply via a reactive class that is toggled on bookmark tap when user is attendee/organizer. Use a ref + setTimeout to remove the class after animation completes.

Alternatives considered:

  • Web Animations API: More flexible but overkill for a simple shake.
  • CSS transition: Insufficient for a multi-step oscillation.