Files
emba-course-solver/openspec/changes/course-description-popups/design.md

69 lines
4.1 KiB
Markdown

## Context
The app is a React (Vite/TypeScript) single-page app with no external UI library. Courses are rendered as clickable buttons in `CourseSelection.tsx`. There is no existing tooltip or popover infrastructure. The app already handles responsive layout via a `useMediaQuery` hook.
Course descriptions and instructors come from a static PDF (`ref/J27 Electives-Course Descriptions.pdf`) plus one supplementary markdown file (`ref/inovation-and-design.md`). The data is stable per cohort (J27).
## Goals / Non-Goals
**Goals:**
- Show course description and instructor(s) inline via a popover triggered by an info icon
- Work identically on desktop and mobile (click/tap, no hover or long-press)
- Keep course description data separate from solver data structures
**Non-Goals:**
- Dynamic fetching of descriptions from an API
- Editing or updating descriptions at runtime
- Changing the Course type or solver logic
- Adding a third-party tooltip/popover library
## Decisions
### 1. Separate data file keyed by course ID
Store descriptions in `app/src/data/courseDescriptions.ts` as a `Record<string, { description: string; instructors: string[] }>` keyed by course ID.
**Why over extending Course type:** Descriptions are long strings unrelated to solver logic. Keeping them separate preserves readability of `courses.ts` and avoids polluting the `Course` interface used throughout the solver.
**Why key by course ID (not name):** The same course name can appear in multiple elective sets with different instructors (e.g., "Collaboration, Conflict and Negotiation" — Steve Blader in Spring, Elizabeth Morrison in Summer). Per-ID keying handles this correctly at the cost of some description duplication.
### 2. Info icon trigger (not hover/long-press)
An `(i)` icon button next to each course name, clickable on all platforms.
**Why over CSS hover tooltip:** Hover doesn't work on touch devices. A separate info icon avoids conflicting with the existing click-to-select behavior on the course button.
**Why over long-press on mobile:** Long-press is discoverable only if users know to try it. An explicit icon is universally obvious.
### 3. Pure CSS/React popover (no library)
Build the popover as a positioned `div` managed with React state. Close on: click outside (document listener), re-click icon, or Escape key.
**Why no library:** The app has zero UI dependencies beyond React. A single popover component doesn't justify adding one. The positioning logic is straightforward since popovers anchor to a known icon element.
### 4. Popover content layout
```
┌────────────────────────────────┐
│ Course Name │
│ Instructor(s): Name, Name │
│ ────────────────────────────── │
│ Description text, scrollable │
│ if longer than max-height... │
└────────────────────────────────┘
```
- Fixed max-width (~320px), max-height (~300px) with overflow scroll
- Instructor list shown as comma-separated
- Close button (X) in top-right corner
### 5. Event handling: stop propagation on info icon
The `(i)` icon click must call `e.stopPropagation()` to prevent the parent course button's `onClick` (which pins the course) from firing.
## Risks / Trade-offs
- **Description duplication across sets** — Same description text stored under multiple course IDs (e.g., `spr1-collaboration` and `sum1-collaboration`). Acceptable given the small dataset (~35 entries) and the need for per-ID instructor differentiation.
- **Popover positioning at screen edges** — A simple anchored popover could overflow the viewport on narrow screens. Mitigation: on mobile, render as a near-full-width card or use `position: fixed` centered overlay instead of anchored positioning.
- **Stale data if courses change** — Descriptions are hardcoded. If the course list changes for a future cohort, both `courses.ts` and `courseDescriptions.ts` need updating. This matches the existing pattern (all course data is static).