Add event list feature (009-list-events)
Enable users to see all their saved events on the home screen, sorted by date with upcoming events first. Key capabilities: - EventCard with title, relative time display, and organizer/attendee role badge - Sortable EventList with past-event visual distinction (faded style) - Empty state when no events are stored - Swipe-to-delete gesture with confirmation dialog - Floating action button for quick event creation - Rename router param :token → :eventToken across all views - useRelativeTime composable (Intl.RelativeTimeFormat) - useEventStorage: add validation, removeEvent(), reactive versioning - Full E2E and unit test coverage for all new components Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
86
specs/009-list-events/plan.md
Normal file
86
specs/009-list-events/plan.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# Implementation Plan: Event List on Home Page
|
||||
|
||||
**Branch**: `009-list-events` | **Date**: 2026-03-08 | **Spec**: `specs/009-list-events/spec.md`
|
||||
**Input**: Feature specification from `/specs/009-list-events/spec.md`
|
||||
|
||||
## Summary
|
||||
|
||||
Transform the home page from a static empty-state placeholder into a dynamic event list that shows all events stored in the browser's localStorage. Each event card displays title, relative time, and role indicator (organizer/attendee). Events are sorted chronologically (upcoming first), past events appear faded, and users can remove events via delete icon or swipe gesture. A FAB provides persistent access to event creation.
|
||||
|
||||
This is a **frontend-only** feature — no backend or API changes required. The existing `useEventStorage` composable already provides all necessary data access.
|
||||
|
||||
## Technical Context
|
||||
|
||||
**Language/Version**: TypeScript 5.9, Vue 3.5
|
||||
**Primary Dependencies**: Vue 3, Vue Router 5, Vite
|
||||
**Storage**: Browser localStorage via `useEventStorage` composable
|
||||
**Testing**: Vitest (unit), Playwright + MSW (E2E)
|
||||
**Target Platform**: Mobile-first PWA (centered 480px column on desktop)
|
||||
**Project Type**: Web application (frontend-only changes)
|
||||
**Performance Goals**: Event list renders within 1 second (SC-001) — trivial given localStorage read
|
||||
**Constraints**: No external dependencies, no tracking, WCAG AA, keyboard navigable
|
||||
**Scale/Scope**: Typically <50 events in localStorage; no pagination needed
|
||||
|
||||
## Constitution Check
|
||||
|
||||
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
||||
|
||||
| Principle | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| I. Privacy by Design | ✅ PASS | Purely client-side. No data leaves the browser. No analytics. |
|
||||
| II. Test-Driven Methodology | ✅ PASS | Unit tests for composable, E2E for each user story. TDD enforced. |
|
||||
| III. API-First Development | ✅ N/A | No API changes — this feature reads only from localStorage. |
|
||||
| IV. Simplicity & Quality | ✅ PASS | Minimal approach: extend existing composable + new components. No over-engineering. |
|
||||
| V. Dependency Discipline | ✅ PASS | No new dependencies. Swipe gesture implemented with native Touch API. Relative time via built-in `Intl.RelativeTimeFormat`. |
|
||||
| VI. Accessibility | ✅ PASS | Semantic list markup, ARIA labels, keyboard navigation, WCAG AA contrast on faded past events. |
|
||||
|
||||
**Gate result: PASS** — no violations.
|
||||
|
||||
## Project Structure
|
||||
|
||||
### Documentation (this feature)
|
||||
|
||||
```text
|
||||
specs/009-list-events/
|
||||
├── plan.md # This file
|
||||
├── spec.md # Feature specification
|
||||
├── research.md # Phase 0 output
|
||||
├── data-model.md # Phase 1 output
|
||||
└── tasks.md # Phase 2 output (/speckit.tasks command)
|
||||
```
|
||||
|
||||
### Source Code (repository root)
|
||||
|
||||
```text
|
||||
frontend/
|
||||
├── src/
|
||||
│ ├── composables/
|
||||
│ │ ├── useEventStorage.ts # MODIFY: add removeEvent()
|
||||
│ │ ├── useRelativeTime.ts # NEW: Intl.RelativeTimeFormat wrapper
|
||||
│ │ └── __tests__/
|
||||
│ │ ├── useEventStorage.spec.ts # MODIFY: add removeEvent tests
|
||||
│ │ └── useRelativeTime.spec.ts # NEW: relative time formatting tests
|
||||
│ ├── components/
|
||||
│ │ ├── EventCard.vue # NEW: individual event list item
|
||||
│ │ ├── EventList.vue # NEW: sorted event list container
|
||||
│ │ ├── EmptyState.vue # NEW: extracted empty state
|
||||
│ │ ├── CreateEventFab.vue # NEW: floating action button
|
||||
│ │ ├── ConfirmDialog.vue # NEW: reusable confirmation prompt
|
||||
│ │ └── __tests__/
|
||||
│ │ ├── EventCard.spec.ts # NEW
|
||||
│ │ ├── EventList.spec.ts # NEW
|
||||
│ │ ├── EmptyState.spec.ts # NEW
|
||||
│ │ └── ConfirmDialog.spec.ts # NEW
|
||||
│ ├── views/
|
||||
│ │ └── HomeView.vue # MODIFY: compose list/empty/fab
|
||||
│ └── assets/
|
||||
│ └── main.css # MODIFY: add event card, faded, fab styles
|
||||
└── e2e/
|
||||
└── home-events.spec.ts # NEW: E2E tests for all user stories
|
||||
```
|
||||
|
||||
**Structure Decision**: Frontend-only changes. New components in `components/`, composable extensions in `composables/`, styles in existing `main.css`. No backend changes.
|
||||
|
||||
## Complexity Tracking
|
||||
|
||||
No constitution violations — this section is intentionally empty.
|
||||
Reference in New Issue
Block a user