4.1 KiB
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-collaborationandsum1-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: fixedcentered overlay instead of anchored positioning. - Stale data if courses change — Descriptions are hardcoded. If the course list changes for a future cohort, both
courses.tsandcourseDescriptions.tsneed updating. This matches the existing pattern (all course data is static).