v1.4.0: Desktop layout redesign + mobile tabs

Specializations move from a 340px left rail to a horizontal 2-row chip
grid at the top (drag L→R to rank). Each chip shows rank, spec-colored
abbreviation tag matching the tags used in plans/schedule, full name on
its own row, status glyph, and a micro credit bar. Hover/tap a chip to
see full status, allocated/threshold credits, and contributing-courses
breakdown in a popover.

The right pane splits into two side-by-side columns on desktop: Top
Plans (left) and Schedule (right), each scrolling independently. The
search progress bar hoists into a global strip below the spec grid so
it stays visible regardless of which column is scrolled.

Schedule blocks render their course choices as a horizontal row of
equal-width buttons (3-5 per set) instead of stacked rows. Pinned sets
collapse to a single line with the course name inline next to the set
title. Term headers (Spring/Summer/Fall) remain as section dividers.

On mobile, the layout becomes a 3-tab segmented control
(Specializations / Plans / Courses) with the search progress strip
above the tabs. The previous floating MobileStatusBanner and
MobileCourseBanner are dropped — tabs replace their navigation
function.
This commit is contained in:
2026-05-09 17:45:28 -04:00
parent b282709476
commit 2ebfb9d2ec
14 changed files with 1293 additions and 205 deletions
@@ -0,0 +1,44 @@
## MODIFIED Requirements
### Requirement: Mobile-first responsive layout
The app SHALL adapt its layout to two viewport classes: mobile (<768px) and desktop (≥768px). On mobile, all panels SHALL stack vertically in a single column. On desktop, the layout SHALL use a top-to-bottom arrangement of: header + mode toggle + mode comparison banner, a horizontal specialization strip, a global search-progress strip, and a 2-column workspace below containing Top Plans (left) and Schedule (right). The two workspace columns SHALL scroll independently while the spec strip and progress strip SHALL remain fixed at the top of the workspace area.
#### Scenario: Mobile viewport
- **WHEN** the viewport width is less than 768px
- **THEN** the layout SHALL display as a single column with the specialization panel above the Top Plans section above the Schedule section, all full-width
#### Scenario: Desktop viewport
- **WHEN** the viewport width is 768px or greater
- **THEN** the layout SHALL display the horizontal specialization strip across the top, a global progress strip directly below it, and a 2-column workspace below the progress strip with Top Plans on the left and Schedule on the right
#### Scenario: Independent column scroll on desktop
- **WHEN** the user is on desktop and scrolls inside the Top Plans column
- **THEN** only the Top Plans column SHALL scroll while the Schedule column, the progress strip, and the specialization strip SHALL remain in place
#### Scenario: Independent column scroll on desktop (Schedule)
- **WHEN** the user is on desktop and scrolls inside the Schedule column
- **THEN** only the Schedule column SHALL scroll while the Top Plans column, the progress strip, and the specialization strip SHALL remain in place
### Requirement: Notification banners span full width
Mode comparison and mutual exclusion warnings SHALL render as full-width banners above the main panel layout, not inside a specific column.
#### Scenario: Warning banner placement
- **WHEN** a mutual exclusion warning or mode comparison banner is active
- **THEN** the banner SHALL appear between the header/mode toggle and the specialization strip (desktop) or the specialization panel (mobile), spanning the full container width
## ADDED Requirements
### Requirement: Global search-progress strip on desktop
On desktop, the active search progress bar SHALL render as a thin full-width strip directly below the specialization strip and above the workspace columns. It SHALL remain visible from any scroll position within either workspace column. On mobile, the progress bar SHALL remain inside the Top Plans section as today.
#### Scenario: Search in progress on desktop
- **WHEN** the decision-tree search is running on desktop
- **THEN** an animated progress bar SHALL render in a strip between the specialization strip and the workspace columns, showing the iteration count and percent complete
#### Scenario: Search complete on desktop
- **WHEN** the decision-tree search has completed on desktop
- **THEN** the progress bar SHALL no longer animate; the static "Search complete · N explored" or "Search incomplete · cap hit at N" status MAY remain visible inline with the Top Plans header
#### Scenario: Search progress on mobile
- **WHEN** the decision-tree search is running on mobile
- **THEN** the progress bar SHALL render inside the Top Plans section, not in a global strip
@@ -0,0 +1,105 @@
## ADDED Requirements
### Requirement: Desktop horizontal course button arrangement
On desktop, each non-pinned elective set in the Schedule column SHALL render its course choices as a horizontal flex row of equal-width buttons (one button per course), with each button stretching to fill its share of the row. Pinned sets SHALL continue to render as today (single-line course name with a Clear button). On mobile, course choices SHALL continue to render as vertically stacked rows.
#### Scenario: Desktop unpinned set renders horizontal row
- **WHEN** the user views an unpinned elective set on desktop
- **THEN** the set's course choices SHALL render as a horizontal flex row of equal-width buttons, one button per course
#### Scenario: Desktop unpinned set with three courses
- **WHEN** an unpinned elective set has 3 courses on desktop
- **THEN** the row SHALL render 3 buttons stretched to fill the available width
#### Scenario: Desktop unpinned set with five courses
- **WHEN** an unpinned elective set has 5 courses on desktop
- **THEN** the row SHALL render 5 buttons stretched to fill the available width, narrower than the 3-course case
#### Scenario: Pinned set on desktop unchanged
- **WHEN** an elective set is pinned on desktop
- **THEN** the set SHALL continue to render the pinned course name with a Clear button on a single line, not as a horizontal row
#### Scenario: Mobile keeps stacked rows
- **WHEN** the user views an unpinned elective set on mobile
- **THEN** the course choices SHALL continue to render as vertically stacked full-width rows
### Requirement: Course button anatomy on desktop
Each course button in a desktop horizontal row SHALL display: an info icon in the top-left when course description info is available, a "recommended" star indicator in the top-right when this course is the recommended choice for the set, the course name with a multi-line clamp (max 2-3 lines) and the full name available via the title attribute, and a row of spec ceiling tags at the bottom showing the specializations this course could contribute to.
#### Scenario: Button with all elements
- **WHEN** a course on desktop is the recommended choice, has a description, and has spec qualifications
- **THEN** the button SHALL show the info icon (top-left), the recommended star (top-right), the course name with line-clamp, and the spec ceiling tags at the bottom
#### Scenario: Course without info
- **WHEN** a course on desktop has no description in `COURSE_DESCRIPTIONS`
- **THEN** the button SHALL omit the info icon while keeping all other elements
#### Scenario: Course not recommended
- **WHEN** a course on desktop is not the recommended choice for its set
- **THEN** the button SHALL omit the recommended star while keeping all other elements
#### Scenario: Long course name truncation
- **WHEN** a course name exceeds the line-clamp limit
- **THEN** the visible text SHALL truncate with an ellipsis and the full name SHALL be available via the title attribute on hover
#### Scenario: Spec tag overflow within button
- **WHEN** a course has more spec tags than fit in a single row inside the button at the current button width
- **THEN** the spec tag row SHALL wrap to multiple lines within the button, and the button heights in the set SHALL flex-stretch to remain aligned
### Requirement: Course button states on desktop
Course buttons on desktop SHALL communicate the following states with distinct styling: cancelled (strikethrough name, gray background, "Cancelled" footer text, non-clickable), already-selected-elsewhere (gray background, "Already selected" footer text, non-clickable), per-course searching (skeleton placeholder where spec tags would render), recommended (recommended star + visual emphasis), and required-for-spec (small amber footer note "Required for X" when the course is required for one or more specializations).
#### Scenario: Cancelled course button
- **WHEN** a course in an unpinned set has `cancelled: true`
- **THEN** its button SHALL render with strikethrough name, gray background, a "(Cancelled)" footer label, and SHALL not be clickable
#### Scenario: Already-selected course button
- **WHEN** a course is in the disabled-because-pinned-elsewhere set
- **THEN** its button SHALL render with gray background, an "(Already selected)" footer label, and SHALL not be clickable
#### Scenario: Per-course searching state
- **WHEN** the decision tree analysis for a course is still in progress (the course's `evaluated` flag is false)
- **THEN** the button SHALL render a pulsing skeleton band where the spec ceiling tags would otherwise appear, with all other content unchanged
#### Scenario: Required-for footer
- **WHEN** a course is required for one or more specializations
- **THEN** the button SHALL render a small amber "Required for X" footer beneath the spec tag row
## MODIFIED Requirements
### Requirement: Inline decision tree ceiling per course option
When decision tree analysis is available for an open elective set, each course option SHALL display its ceiling outcome (spec abbreviations) as small spec tags. On mobile, the spec tags SHALL render on the right side of each course row. On desktop, the spec tags SHALL render in a row along the bottom of each course button.
#### Scenario: Mobile ceiling data on right side
- **WHEN** an open set has completed decision tree analysis on mobile and a course has ceiling specs (BNK, FIN, LCM)
- **THEN** the course row SHALL show the course name on the left and tags for BNK, FIN, LCM on the right
#### Scenario: Desktop ceiling data along bottom
- **WHEN** an open set has completed decision tree analysis on desktop and a course has ceiling specs (BNK, FIN, LCM)
- **THEN** the course button SHALL show the course name with line-clamp at top and a row of tags for BNK, FIN, LCM along the bottom of the button
#### Scenario: Ceiling data not yet available
- **WHEN** an open set's decision tree analysis is still computing
- **THEN** the course buttons (mobile rows or desktop buttons) SHALL render without ceiling tags, and the set header SHALL show a subtle loading indicator
#### Scenario: Pinned set does not show ceiling
- **WHEN** a set has a pinned course selection
- **THEN** the set SHALL display the pinned course name without ceiling data on either mobile or desktop (same as current behavior)
### Requirement: High impact indicator on set header
When a set has high impact (variance > 0 in ceiling outcomes), the set header SHALL display a "high impact" indicator. This applies on both mobile and desktop.
#### Scenario: High impact set on mobile or desktop
- **WHEN** an open set's analysis shows impact > 0
- **THEN** the set header SHALL display a "high impact" label next to the set name
### Requirement: No standalone decision tree section
The standalone DecisionTree component at the bottom of the results dashboard SHALL be removed. All ceiling data SHALL be displayed inline within the course selection panel — on mobile as right-side row content, on desktop as bottom-of-button content.
#### Scenario: All tree data inline on mobile
- **WHEN** the user views the course selection panel on mobile
- **THEN** there SHALL be no separate "Decision Tree" heading or section; all ceiling outcomes appear within their respective elective set cards as right-side row content
#### Scenario: All tree data inline on desktop
- **WHEN** the user views the Schedule column on desktop
- **THEN** there SHALL be no separate "Decision Tree" heading or section; all ceiling outcomes appear within their respective elective set cards as bottom-of-button content
@@ -0,0 +1,107 @@
## ADDED Requirements
### Requirement: Desktop horizontal specialization strip
On desktop, the specialization panel SHALL render as a single-row horizontal strip of compact chips, one chip per specialization, ordered left-to-right by priority rank. Each chip SHALL display the rank number, the specialization's 3-letter abbreviation, and a micro credit progress bar. The chip's background color SHALL encode the specialization's status using the same four-color palette used for vertical rows on mobile (achieved, achievable, missing required, unreachable).
#### Scenario: Strip rendering on desktop
- **WHEN** the user views the app on desktop
- **THEN** all specializations SHALL render as a horizontal row of compact chips in priority order (highest priority leftmost)
#### Scenario: Chip status color
- **WHEN** a specialization is in the "achieved" status
- **THEN** its chip SHALL render with the achieved-status background color from `STATUS_STYLES`
#### Scenario: Chip content
- **WHEN** a specialization chip renders on desktop
- **THEN** the chip SHALL display the rank number, the 3-letter abbreviation (e.g., "FIN"), and a micro credit progress bar reflecting allocated credits versus the 9-credit threshold
#### Scenario: Strip overflow on narrow desktop viewports
- **WHEN** the desktop viewport width is too narrow to fit all chips at their target width
- **THEN** the strip SHALL allow horizontal scrolling within itself rather than wrap to multiple rows
### Requirement: Specialization chip hover popover
On desktop, hovering or tapping a specialization chip SHALL open a popover anchored to the chip showing the full specialization name, the status word ("Achieved" / "Achievable" / "Missing Required" / "Unreachable"), the allocated/threshold credits in numeric form (e.g., "5.5 / 9.0"), and — for achieved specializations — the contributing-courses breakdown (each contributing course name and its credit amount). The popover SHALL stay open while the cursor is over either the chip or the popover, and SHALL close when the cursor leaves both.
#### Scenario: Hover opens popover
- **WHEN** the user moves the cursor over a specialization chip on desktop
- **THEN** a popover SHALL open anchored to the chip showing the full name, status word, and allocated/threshold credits
#### Scenario: Popover for achieved specialization
- **WHEN** the user hovers a chip whose specialization is achieved
- **THEN** the popover SHALL additionally list each contributing course's name and its credit amount
#### Scenario: Popover for non-achieved specialization
- **WHEN** the user hovers a chip whose specialization is not in the achieved status
- **THEN** the popover SHALL omit the contributing-courses breakdown but SHALL still show the full name, status word, and allocated/threshold credits
#### Scenario: Tap toggles popover on touch
- **WHEN** the user taps a chip on a touch device using the desktop layout
- **THEN** the popover SHALL toggle open or closed; tapping outside the chip and popover SHALL close it
#### Scenario: Popover stays open while cursor moves to popover
- **WHEN** the user hovers a chip to open the popover and then moves the cursor onto the popover
- **THEN** the popover SHALL remain open
#### Scenario: Popover does not clip at viewport edges
- **WHEN** a chip near the right edge of the viewport is hovered
- **THEN** the popover SHALL be repositioned horizontally so that it remains within the viewport
- **WHEN** a chip is near the bottom of available space
- **THEN** the popover SHALL flip above the chip if there is more space above than below
### Requirement: Horizontal drag-to-reorder on desktop
On desktop, the specialization chip strip SHALL support drag-and-drop reordering using a left-to-right horizontal sorting strategy. Dragging a chip and dropping it at a new horizontal position SHALL update the priority ranking accordingly.
#### Scenario: Drag chip left or right
- **WHEN** the user drags a specialization chip to a new horizontal position on desktop
- **THEN** the priority ranking SHALL update to reflect the new left-to-right order (leftmost chip is highest priority)
#### Scenario: Mobile keeps vertical drag
- **WHEN** the user drags a specialization on mobile
- **THEN** reordering SHALL continue to use vertical drag semantics
## MODIFIED Requirements
### Requirement: Specialization rows include credit progress
On mobile, each specialization row in the ranking list SHALL display the credit progress bar and allocated/threshold credits alongside the rank, name, and status badge. The row layout SHALL be: reorder controls, rank number, name, credits (e.g. "7.5 / 9.0"), status badge, with a credit bar below. On desktop the equivalent information SHALL render in the chip + hover-popover combination defined in this capability rather than as full rows.
#### Scenario: Mobile row displays allocated credits and bar
- **WHEN** a specialization has 7.5 allocated credits from pinned courses on mobile
- **THEN** the row SHALL show "7.5 / 9.0" and a credit progress bar filled to 7.5/9.0
#### Scenario: Mobile row displays zero credits
- **WHEN** a specialization has no allocated credits on mobile
- **THEN** the row SHALL show "0 / 9.0" and an empty credit progress bar with the 9-credit threshold marker visible
#### Scenario: Desktop chip displays credit progress
- **WHEN** a specialization has any allocated credits on desktop
- **THEN** the chip SHALL show a micro credit bar reflecting the allocated proportion against the 9-credit threshold, and the popover SHALL display the numeric "X.X / 9.0" value
### Requirement: Expandable allocation breakdown
On mobile, achieved specialization rows SHALL be tappable/clickable to expand and show the allocation breakdown (which courses contribute how many credits). On desktop, the allocation breakdown SHALL render inside the chip's hover popover for achieved specializations rather than via in-place expansion.
#### Scenario: Mobile tap to expand achieved spec
- **WHEN** a user taps an achieved specialization row on mobile
- **THEN** the row SHALL expand to show a list of contributing courses and their credit amounts
#### Scenario: Mobile tap to collapse
- **WHEN** a user taps an already-expanded achieved specialization row on mobile
- **THEN** the allocation breakdown SHALL collapse
#### Scenario: Mobile non-achieved specs are not expandable
- **WHEN** a user taps a specialization that is not achieved on mobile
- **THEN** nothing SHALL happen (no expand/collapse)
#### Scenario: Desktop allocation breakdown via popover
- **WHEN** a user hovers an achieved specialization chip on desktop
- **THEN** the chip's popover SHALL show the contributing-courses breakdown (each course name and its credit amount); no in-place expansion of the chip occurs
### Requirement: Achievement summary
The panel SHALL display a summary count showing how many specializations are currently achieved. On mobile, the summary appears above the ranking list. On desktop, the summary appears in the specialization strip's header area (above or alongside the strip).
#### Scenario: Some achieved
- **WHEN** 2 specializations are achieved
- **THEN** the panel SHALL display "2 specializations achieved" (mobile: above the list; desktop: in the strip header)
#### Scenario: None achieved
- **WHEN** no specializations are achieved
- **THEN** the panel SHALL display "No specializations achieved yet" (mobile: above the list; desktop: in the strip header)