Group events into five temporal sections with section headers, date subheaders, and context-aware time display (clock time for upcoming, relative for past). Includes new useEventGrouping composable, SectionHeader and DateSubheader components, full unit and E2E test coverage. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
5.6 KiB
Research: Event List Temporal Grouping
Feature: 010-event-list-grouping | Date: 2026-03-08
1. Week Boundary Calculation
Decision: Use ISO 8601 week convention (Monday = first day of week). "This Week" spans from tomorrow through Sunday of the current week.
Rationale: The spec explicitly states "ISO convention where Monday is the first day of the week" (Assumptions section). The browser's Date.getDay() returns 0 for Sunday, 1 for Monday — straightforward to compute end-of-week as next Sunday 23:59:59.
Implementation: Compare event date against:
startOfTodayandendOfTodayfor "Today"startOfTomorrowandendOfSundayfor "This Week"after endOfSundayfor "Later"before startOfTodayfor "Past"
Edge case (spec scenario 4): On Sunday, "This Week" is empty (tomorrow is already next week Monday), so events for Monday appear under "Later". This falls out naturally from the algorithm.
Alternatives considered:
- Using a date library (date-fns, luxon): Rejected — dependency discipline (Constitution V). Native
Date+Intlis sufficient for this logic. - Locale-dependent week start: Rejected — spec mandates ISO convention explicitly.
2. Date Formatting for Subheaders
Decision: Use Intl.DateTimeFormat with { weekday: 'short', day: 'numeric', month: 'short' } to produce labels like "Wed, 12 Mar".
Rationale: Consistent with existing use of Intl.RelativeTimeFormat in useRelativeTime.ts. Respects user locale for month/weekday names. No external dependency needed.
Alternatives considered:
- Hardcoded English day/month names: Rejected — the project already uses
IntlAPIs for locale awareness. - Full date format (e.g., "Wednesday, March 12, 2026"): Rejected — too long for mobile cards.
3. Time Display on Event Cards
Decision: Add a timeDisplayMode prop to EventCard.vue with two modes:
'clock': Shows formatted time (e.g., "18:30") usingIntl.DateTimeFormatwith{ hour: '2-digit', minute: '2-digit' }'relative': Shows relative time (e.g., "3 days ago") using existingformatRelativeTime()
Rationale: Spec requires different time representations per section: clock time for Today/This Week/Later, relative time for Past. A prop-driven approach keeps EventCard stateless regarding section context.
Alternatives considered:
- EventCard determining its own display mode: Rejected — card shouldn't know about sections; parent owns that context.
- Passing a pre-formatted string: Viable but less type-safe. A mode enum is clearer.
4. Grouping Data Structure
Decision: The useEventGrouping composable returns an array of section objects:
interface EventSection {
key: 'today' | 'thisWeek' | 'later' | 'past'
label: string // "Today", "This Week", "Later", "Past"
events: GroupedEvent[]
}
interface DateGroup {
date: string // ISO date string (YYYY-MM-DD) for keying
label: string // Formatted date label (e.g., "Wed, 12 Mar")
events: StoredEvent[]
}
interface GroupedEvent extends StoredEvent {
dateGroup: string // ISO date for sub-grouping
}
Actually, simpler: the composable returns sections, each containing date groups, each containing events.
interface EventSection {
key: 'today' | 'thisWeek' | 'later' | 'past'
label: string
dateGroups: DateGroup[]
}
interface DateGroup {
dateKey: string // YYYY-MM-DD
label: string // Formatted: "Wed, 12 Mar"
events: StoredEvent[]
}
Rationale: Two-level grouping (section → date → events) matches the spec's hierarchy. Empty sections are simply omitted from the returned array (FR-002). The "Today" section still has one DateGroup but the template skips rendering its subheader (FR-005).
Alternatives considered:
- Flat list with section markers: Harder to template, mixes data and presentation.
- Map/Record structure: Arrays preserve ordering guarantees (Today → This Week → Later → Past).
5. Visual Emphasis for "Today" Section
Decision: Apply a CSS class .section--today to the Today section that uses:
- Slightly larger section header (font-weight: 800, font-size: 1.1rem vs 700/1rem for others)
- A subtle left border accent using the primary gradient pink (
#F06292)
Rationale: Consistent with Electric Dusk design system. Subtle enough not to distract but visually distinct. The existing past-event fade (opacity: 0.6, saturate: 0.5) already handles the other end of the spectrum.
Alternatives considered:
- Background highlight: Could clash with card backgrounds on mobile.
- Icon/emoji prefix: Spec doesn't mention icons; keep it typography-driven per design system.
6. Accessibility Considerations
Decision:
- Section headers are
<h2>elements - Date subheaders are
<h3>elements - The event list container keeps its existing
role="list" - Each section is a
<section>element witharia-labelmatching the section label
Rationale: Constitution VI requires semantic HTML and ARIA. The heading hierarchy (h2 > h3) provides screen reader navigation landmarks. The <section> element with label allows assistive technology to announce section boundaries.
7. Existing Test Updates
Decision:
- Existing
EventList.spec.tsunit tests will be updated to account for the new grouped structure (sections instead of flat list) - Existing
home-events.spec.tsE2E tests will be extended with new scenarios for temporal grouping - New
useEventGrouping.spec.tstests the pure grouping function in isolation
Rationale: TDD (Constitution II). The grouping logic is a pure function — ideal for thorough unit testing with various date combinations and edge cases.