Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0beafb58b5 | |||
| 8b88402ecd | |||
| 578c87d59d | |||
| 1907e266c1 | |||
| 441d61abc3 |
14
CHANGELOG.md
14
CHANGELOG.md
@@ -1,5 +1,19 @@
|
||||
# Changelog
|
||||
|
||||
## v1.2.1 — 2026-03-27
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Achievable status accuracy** — specializations marked "Achievable" are now verified via LP feasibility check against already-achieved specs; previously, a specialization could show "Achievable" based on raw credit potential while actually being infeasible due to credit sharing with higher-priority achieved specializations
|
||||
|
||||
## v1.2.0 — 2026-03-27
|
||||
|
||||
### Changes
|
||||
|
||||
- **Course info popovers** — each course now has an info icon that opens a popover showing the course description, instructor(s), and specialization tags, extracted from the J27 Electives PDF; opens on hover (desktop) or tap (mobile), with smart positioning that flips above when near the bottom of the viewport
|
||||
- **Page title and favicon** — updated browser tab from "app" with Vite icon to "EMBA Specialization Solver" with a graduation cap favicon in NYU Stern purple
|
||||
- **Viewport-fitted layout** — desktop layout now fits within the viewable area without page-level scrolling; each pane scrolls independently
|
||||
|
||||
## v1.1.1 — 2026-03-27
|
||||
|
||||
### Changes
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>app</title>
|
||||
<title>EMBA Specialization Solver</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
7
app/public/favicon.svg
Normal file
7
app/public/favicon.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<polygon points="32,8 2,24 32,40 62,24" fill="#57068c"/>
|
||||
<polygon points="32,40 2,24 2,28 32,44 62,28 62,24" fill="#3d0066"/>
|
||||
<rect x="52" y="24" width="2" height="22" fill="#57068c"/>
|
||||
<circle cx="53" cy="48" r="3" fill="#57068c"/>
|
||||
<path d="M16,32 v10 c0,6 16,10 16,10 s16,-4 16,-10 v-10 l-16,8z" fill="#7b2fbe"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 392 B |
@@ -81,11 +81,12 @@ function App() {
|
||||
margin: '0 auto',
|
||||
padding: isMobile ? '12px' : '20px',
|
||||
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
||||
...(isMobile ? {} : { height: '100vh', display: 'flex', flexDirection: 'column', overflow: 'hidden' }),
|
||||
};
|
||||
|
||||
const panelStyle: React.CSSProperties = isMobile
|
||||
? { display: 'flex', flexDirection: 'column', gap: '20px' }
|
||||
: { display: 'grid', gridTemplateColumns: '340px 1fr', gap: '24px', alignItems: 'start' };
|
||||
: { display: 'grid', gridTemplateColumns: '340px 1fr', gap: '24px', alignItems: 'stretch', flex: 1, minHeight: 0 };
|
||||
|
||||
return (
|
||||
<div style={containerStyle}>
|
||||
@@ -118,7 +119,7 @@ function App() {
|
||||
/>
|
||||
|
||||
<div style={panelStyle}>
|
||||
<div ref={specSectionRef} style={isMobile ? {} : { maxHeight: '85vh', overflowY: 'auto' }}>
|
||||
<div ref={specSectionRef} style={isMobile ? {} : { overflowY: 'auto', minHeight: 0 }}>
|
||||
<CreditLegend />
|
||||
<SpecializationRanking
|
||||
ranking={state.ranking}
|
||||
@@ -126,7 +127,7 @@ function App() {
|
||||
onReorder={reorder}
|
||||
/>
|
||||
</div>
|
||||
<div ref={courseSectionRef} style={isMobile ? {} : { maxHeight: '85vh', overflowY: 'auto' }}>
|
||||
<div ref={courseSectionRef} style={isMobile ? {} : { overflowY: 'auto', minHeight: 0 }}>
|
||||
<CourseSelection
|
||||
pinnedCourses={state.pinnedCourses}
|
||||
treeResults={treeResults}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { ELECTIVE_SETS } from '../data/electiveSets';
|
||||
import { SPECIALIZATIONS } from '../data/specializations';
|
||||
import { coursesBySet } from '../data/lookups';
|
||||
import { COURSE_DESCRIPTIONS } from '../data/courseDescriptions';
|
||||
import { courseById } from '../data/lookups';
|
||||
import { useMediaQuery } from '../hooks/useMediaQuery';
|
||||
import type { Term } from '../data/types';
|
||||
import type { SetAnalysis } from '../solver/decisionTree';
|
||||
|
||||
@@ -12,6 +16,164 @@ for (const spec of SPECIALIZATIONS) {
|
||||
}
|
||||
}
|
||||
|
||||
// specId → full spec name
|
||||
const specNameById: Record<string, string> = {};
|
||||
for (const spec of SPECIALIZATIONS) {
|
||||
specNameById[spec.id] = spec.name;
|
||||
}
|
||||
|
||||
function CourseInfoPopover({
|
||||
courseId,
|
||||
courseName,
|
||||
anchorRect,
|
||||
onClose,
|
||||
onHoverEnter,
|
||||
onHoverLeave,
|
||||
}: {
|
||||
courseId: string;
|
||||
courseName: string;
|
||||
anchorRect: DOMRect | null;
|
||||
onClose: () => void;
|
||||
onHoverEnter: () => void;
|
||||
onHoverLeave: () => void;
|
||||
}) {
|
||||
const popoverRef = useRef<HTMLDivElement>(null);
|
||||
const breakpoint = useMediaQuery();
|
||||
const isMobile = breakpoint === 'mobile';
|
||||
const info = COURSE_DESCRIPTIONS[courseId];
|
||||
|
||||
// Close on Escape
|
||||
useEffect(() => {
|
||||
function onKeyDown(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape') onClose();
|
||||
}
|
||||
document.addEventListener('keydown', onKeyDown);
|
||||
return () => document.removeEventListener('keydown', onKeyDown);
|
||||
}, [onClose]);
|
||||
|
||||
// Close on click outside
|
||||
useEffect(() => {
|
||||
function onClickOutside(e: MouseEvent) {
|
||||
if (popoverRef.current && !popoverRef.current.contains(e.target as Node)) {
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
// Defer so the opening click doesn't immediately close
|
||||
const id = setTimeout(() => document.addEventListener('mousedown', onClickOutside), 0);
|
||||
return () => {
|
||||
clearTimeout(id);
|
||||
document.removeEventListener('mousedown', onClickOutside);
|
||||
};
|
||||
}, [onClose]);
|
||||
|
||||
if (!info) return null;
|
||||
|
||||
const instructorLabel = info.instructors.length > 1 ? 'Instructors' : 'Instructor';
|
||||
const instructorText = info.instructors.length > 0
|
||||
? info.instructors.join(', ')
|
||||
: null;
|
||||
|
||||
// Mobile: centered fixed overlay. Desktop: anchored near the icon, flipping above if needed.
|
||||
const popoverMaxHeight = 300;
|
||||
const spaceBelow = anchorRect ? window.innerHeight - anchorRect.bottom - 6 : popoverMaxHeight;
|
||||
const spaceAbove = anchorRect ? anchorRect.top - 6 : popoverMaxHeight;
|
||||
const placeAbove = spaceBelow < Math.min(popoverMaxHeight, 150) && spaceAbove > spaceBelow;
|
||||
|
||||
const positionStyle: React.CSSProperties = isMobile
|
||||
? {
|
||||
position: 'fixed',
|
||||
left: '16px',
|
||||
right: '16px',
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
maxWidth: 'calc(100vw - 32px)',
|
||||
}
|
||||
: {
|
||||
position: 'fixed',
|
||||
left: anchorRect ? Math.min(anchorRect.left, window.innerWidth - 340) : 0,
|
||||
...(placeAbove
|
||||
? { bottom: anchorRect ? window.innerHeight - anchorRect.top + 6 : 0, maxHeight: Math.min(popoverMaxHeight, spaceAbove) }
|
||||
: { top: anchorRect ? anchorRect.bottom + 6 : 0, maxHeight: Math.min(popoverMaxHeight, spaceBelow) }),
|
||||
maxWidth: '320px',
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isMobile && (
|
||||
<div style={{
|
||||
position: 'fixed', inset: 0,
|
||||
background: 'rgba(0,0,0,0.3)',
|
||||
zIndex: 999,
|
||||
}} />
|
||||
)}
|
||||
<div
|
||||
ref={popoverRef}
|
||||
onMouseEnter={onHoverEnter}
|
||||
onMouseLeave={onHoverLeave}
|
||||
style={{
|
||||
...positionStyle,
|
||||
zIndex: 1000,
|
||||
background: '#fff',
|
||||
border: '1px solid #d1d5db',
|
||||
borderRadius: '8px',
|
||||
boxShadow: '0 4px 16px rgba(0,0,0,0.15)',
|
||||
padding: '12px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '6px' }}>
|
||||
<div style={{ fontWeight: 600, fontSize: '14px', color: '#1e293b', flex: 1, paddingRight: '8px' }}>
|
||||
{courseName}
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
style={{
|
||||
background: 'none', border: 'none', cursor: 'pointer',
|
||||
fontSize: '16px', color: '#94a3b8', padding: '0 2px',
|
||||
lineHeight: 1, flexShrink: 0,
|
||||
}}
|
||||
aria-label="Close"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
{instructorText && (
|
||||
<div style={{ fontSize: '12px', color: '#6366f1', marginBottom: '6px', fontWeight: 500 }}>
|
||||
{instructorLabel}: {instructorText}
|
||||
</div>
|
||||
)}
|
||||
{courseById[courseId]?.qualifications.length > 0 && (
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '4px', marginBottom: '8px' }}>
|
||||
{courseById[courseId].qualifications.map((q) => (
|
||||
<span
|
||||
key={q.specId}
|
||||
title={specNameById[q.specId]}
|
||||
style={{
|
||||
fontSize: '10px', fontWeight: 600,
|
||||
padding: '2px 6px', borderRadius: '3px',
|
||||
background: '#f0f0ff', color: '#4338ca',
|
||||
border: '1px solid #e0e0ff',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{q.specId}{q.marker !== 'standard' ? ` (${q.marker})` : ''}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div style={{
|
||||
fontSize: '12px', color: '#475569', lineHeight: 1.5,
|
||||
overflowY: 'auto', flex: 1,
|
||||
whiteSpace: 'pre-line',
|
||||
}}>
|
||||
{info.description}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface CourseSelectionProps {
|
||||
pinnedCourses: Record<string, string | null>;
|
||||
treeResults: SetAnalysis[];
|
||||
@@ -31,6 +193,11 @@ function ElectiveSet({
|
||||
disabledCourseIds,
|
||||
onPin,
|
||||
onUnpin,
|
||||
openPopoverId,
|
||||
onOpenPopover,
|
||||
onClosePopover,
|
||||
onHoverOpen,
|
||||
onHoverLeave,
|
||||
}: {
|
||||
setId: string;
|
||||
setName: string;
|
||||
@@ -40,6 +207,11 @@ function ElectiveSet({
|
||||
disabledCourseIds: Set<string>;
|
||||
onPin: (courseId: string) => void;
|
||||
onUnpin: () => void;
|
||||
openPopoverId: string | null;
|
||||
onOpenPopover: (courseId: string, rect: DOMRect) => void;
|
||||
onClosePopover: () => void;
|
||||
onHoverOpen: (courseId: string, rect: DOMRect) => void;
|
||||
onHoverLeave: () => void;
|
||||
}) {
|
||||
const courses = coursesBySet[setId];
|
||||
const isPinned = pinnedCourseId != null;
|
||||
@@ -110,6 +282,7 @@ function ElectiveSet({
|
||||
const ceiling = ceilingMap.get(course.id);
|
||||
const reqFor = requiredForSpec[course.id];
|
||||
const showSkeleton = loading && !analysis;
|
||||
const hasInfo = !!COURSE_DESCRIPTIONS[course.id];
|
||||
return (
|
||||
<button
|
||||
key={course.id}
|
||||
@@ -131,6 +304,9 @@ function ElectiveSet({
|
||||
flex: 1,
|
||||
textDecoration: isCancelled ? 'line-through' : 'none',
|
||||
fontStyle: isCancelled ? 'italic' : 'normal',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '4px',
|
||||
}}>
|
||||
{course.name}
|
||||
{isCancelled && (
|
||||
@@ -143,6 +319,57 @@ function ElectiveSet({
|
||||
(Already selected)
|
||||
</span>
|
||||
)}
|
||||
{!isUnavailable && hasInfo && (
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-label={`Info about ${course.name}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (openPopoverId === course.id) {
|
||||
onClosePopover();
|
||||
} else {
|
||||
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
||||
onOpenPopover(course.id, rect);
|
||||
}
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (window.matchMedia('(hover: hover)').matches) {
|
||||
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
||||
onHoverOpen(course.id, rect);
|
||||
}
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
if (window.matchMedia('(hover: hover)').matches) {
|
||||
onHoverLeave();
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (openPopoverId === course.id) {
|
||||
onClosePopover();
|
||||
} else {
|
||||
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
||||
onOpenPopover(course.id, rect);
|
||||
}
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
|
||||
width: '16px', height: '16px', borderRadius: '50%',
|
||||
border: '1px solid #cbd5e1', background: openPopoverId === course.id ? '#e0e7ff' : '#f1f5f9',
|
||||
color: '#6366f1', fontSize: '10px', fontWeight: 700,
|
||||
cursor: 'pointer', flexShrink: 0,
|
||||
fontStyle: 'normal', textDecoration: 'none',
|
||||
lineHeight: 1,
|
||||
}}
|
||||
>
|
||||
i
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
{!isUnavailable && showSkeleton ? (
|
||||
<span
|
||||
@@ -193,6 +420,44 @@ export function CourseSelection({ pinnedCourses, treeResults, treeLoading, disab
|
||||
// Index tree results by setId for O(1) lookup
|
||||
const treeBySet = new Map(treeResults.map((a) => [a.setId, a]));
|
||||
|
||||
// Single popover state: only one open at a time
|
||||
const [popover, setPopover] = useState<{ courseId: string; courseName: string; anchorRect: DOMRect } | null>(null);
|
||||
const hoverCloseTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
const cancelHoverClose = useCallback(() => {
|
||||
if (hoverCloseTimer.current) {
|
||||
clearTimeout(hoverCloseTimer.current);
|
||||
hoverCloseTimer.current = null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleOpenPopover = useCallback((courseId: string, rect: DOMRect) => {
|
||||
cancelHoverClose();
|
||||
const allCourses = Object.values(coursesBySet).flat();
|
||||
const course = allCourses.find(c => c.id === courseId);
|
||||
setPopover({ courseId, courseName: course?.name ?? '', anchorRect: rect });
|
||||
}, [cancelHoverClose]);
|
||||
|
||||
const handleClosePopover = useCallback(() => {
|
||||
cancelHoverClose();
|
||||
setPopover(null);
|
||||
}, [cancelHoverClose]);
|
||||
|
||||
// Hover open: same as click open but cancels any pending close
|
||||
const handleHoverOpen = useCallback((courseId: string, rect: DOMRect) => {
|
||||
cancelHoverClose();
|
||||
const allCourses = Object.values(coursesBySet).flat();
|
||||
const course = allCourses.find(c => c.id === courseId);
|
||||
setPopover({ courseId, courseName: course?.name ?? '', anchorRect: rect });
|
||||
}, [cancelHoverClose]);
|
||||
|
||||
// Hover leave: delayed close so mouse can move from icon to popover
|
||||
const handleHoverLeave = useCallback(() => {
|
||||
hoverCloseTimer.current = setTimeout(() => {
|
||||
setPopover(null);
|
||||
}, 150);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<style>{skeletonStyle}</style>
|
||||
@@ -230,10 +495,25 @@ export function CourseSelection({ pinnedCourses, treeResults, treeLoading, disab
|
||||
disabledCourseIds={disabledCourseIds}
|
||||
onPin={(courseId) => onPin(set.id, courseId)}
|
||||
onUnpin={() => onUnpin(set.id)}
|
||||
openPopoverId={popover?.courseId ?? null}
|
||||
onOpenPopover={handleOpenPopover}
|
||||
onClosePopover={handleClosePopover}
|
||||
onHoverOpen={handleHoverOpen}
|
||||
onHoverLeave={handleHoverLeave}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
{popover && (
|
||||
<CourseInfoPopover
|
||||
courseId={popover.courseId}
|
||||
courseName={popover.courseName}
|
||||
anchorRect={popover.anchorRect}
|
||||
onClose={handleClosePopover}
|
||||
onHoverEnter={cancelHoverClose}
|
||||
onHoverLeave={handleHoverLeave}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
218
app/src/data/courseDescriptions.ts
Normal file
218
app/src/data/courseDescriptions.ts
Normal file
@@ -0,0 +1,218 @@
|
||||
/**
|
||||
* Course descriptions and instructors extracted from the J27 Electives-Course Descriptions PDF
|
||||
* and supplementary sources. Keyed by course ID to handle per-set instructor differences.
|
||||
*/
|
||||
export interface CourseInfo {
|
||||
description: string;
|
||||
instructors: string[];
|
||||
}
|
||||
|
||||
export const COURSE_DESCRIPTIONS: Record<string, CourseInfo> = {
|
||||
// === Spring Elective Set 1 ===
|
||||
'spr1-global-immersion': {
|
||||
description: 'As part of the EMBA Program, students may participate in an optional second-year Global Immersion Experience, in addition to the required first-year GIE. Like the required GIE, this course enables students to gain an understanding of doing business in a given global region through exposure to business leaders and local organizations and institutions. Students travel internationally to a unique global market. In groups, they study the local market and produce a final business idea proposal based on learnings garnered during the GIE.',
|
||||
instructors: [],
|
||||
},
|
||||
'spr1-collaboration': {
|
||||
description: 'Negotiation is the process by which people work together to achieve mutually agreeable outcomes and/or resolve differences. This course provides students with frameworks and concepts for understanding the negotiation process and the opportunity to improve their negotiation and conflict resolution skills. The learning method is experiential. Students engage in a series of negotiation simulations designed to give them experience with a range of different types of negotiations. Emphasis is on principles, strategies and tactics for reaching effective outcomes.',
|
||||
instructors: ['Steve Blader'],
|
||||
},
|
||||
'spr1-high-stakes': {
|
||||
description: 'As a business leader you are expected to give direction, establish vision and manage career events with well-honed, clear messages and constant communication clarity. Achieving and maintaining this level of excellence is easier said than done. Conquering High Stakes Communications focuses on the tactics and skills you need not only to manage critical conversations, but also to identify them in advance with the objective of maneuvering the ultimate outcome to your advantage. This course quickly reinforces core communication skills and teaches advance communication techniques through intensive drills and immersive scenario-based role-playing sessions. It also personalizes the experience by having students bring "real life" managerial and communication challenges they may currently be facing to the course for assessment and strategy setting. This approach allows students to take what they are learning and immediately put these skills, strategies, and techniques into action at work and outside the classroom.',
|
||||
instructors: ['Steve Mellas', 'Jim Donofrio'],
|
||||
},
|
||||
|
||||
// === Spring Elective Set 2 ===
|
||||
'spr2-consumer-behavior': {
|
||||
description: 'Each of us are active consumers in everyday life, purchasing everything from groceries to clothing to college educations. However, our individual personalities and characteristics dictate that no two consumers are alike; we make unique choices, making it challenging for marketers to create coherent marketing strategies regardless of similarities within target markets. In this class, we will examine how and why consumers behave the way they do, how environmental impacts shape how we behave, and the practical marketing implications of that behavior.',
|
||||
instructors: ['Radhika Duggal'],
|
||||
},
|
||||
'spr2-health-medical': {
|
||||
description: 'This course is designed to give the student a general understanding of applied economics of healthcare. In particular, it provides an advanced critical analysis of the delivery of healthcare services and how it is constantly changing. It evaluates the responses of major players including hospitals, physicians, payers, life sciences and new entrants to the market. As expectations for a unified, efficient, cost effective, and high quality global system continue to be desired, the entire ecosystem is trying to adapt. At the end of the course the student will understand why economics in Healthcare is constantly changing and the major drivers impacting the system. Healthcare is the perfect industry to see how market failures occur and why innovation, government regulation, and other interventions may be necessary. While this course is an economics course, it is focused on the application of principles in real life settings and uses current events to highlight their impact. We also try to touch upon regulatory reform and understand how each constituent has an impact on the others within the system. In order to bring in other perspectives, c-suite level executives are brought in as guests.',
|
||||
instructors: ['Clifford Bleustein'],
|
||||
},
|
||||
'spr2-human-rights': {
|
||||
description: 'This course will explore some of the choices global businesses face in addressing human rights challenges in their core business operations. The course will include a series of case studies from different industries where accelerating globalization and advancing technology have made these challenges more acute in recent years. The course will focus on the evolving public face of these issues driven in part by rapid developments in the global media and advances in information technology.',
|
||||
instructors: ['Michael Posner'],
|
||||
},
|
||||
'spr2-financial-services': {
|
||||
description: 'The financial services industry touches all of our lives and has been going through a continuous transformation since the deregulation that began in the early 1970\'s. That evolution has accelerated in recent years as more and more pressure has been brought to bear by various stakeholders in the industry who have divergent goals and agendas. Those invested stakeholders include clients, investors, employees, regulators/politicians and the public at large. Overlaying all of this change has been rapid technological advancement that has had a direct impact on how the industry delivers its services, meets expected equity returns and manages the risk inherent in that delivery. This course is an Advanced Finance Elective where THINKING not MEMORIZING is what it is all about.\n\nThis course is a survey course. It provides a broad overview of the financial services industry and of the forces that are continuing to change it worldwide. That change/evolution has resulted in a confederation of sometimes integrated products and services within a multi-product firm. It has also resulted in individual stand-alone businesses within those same integrated financial firms, or in boutique, stand-alone, limited product firms. The course focuses on four big questions: (1) Why and what kind of services are provided by participants in the industry? (2) Who develops, provides and regulates those services? (3) How are they likely to be executed or modified in the future? (4) What skills (both technical and "soft") are required for an individual to succeed in the industry?',
|
||||
instructors: ['James Finch'],
|
||||
},
|
||||
|
||||
// === Spring Elective Set 3 ===
|
||||
'spr3-mergers-acquisitions': {
|
||||
description: 'This course is designed to take an interdisciplinary approach to understanding the problems of formulating and implementing successful acquisition strategies. Our major objectives are (1) to enable you to act as a senior advisor to your CEO regarding strategic M&A and shareholder value issues your division or company might confront and (2) to assist you in becoming an informed consumer of just about anything written on M&A success (including pitches by professional services providers).\n\nWe will introduce a framework for thinking about acquisitions as a strategic investment where the bottom line is superior shareholder performance. The course will approach acquisitions as a multi-step strategic and organizational process drawing from the fields of strategy, negotiations, finance and organizational behavior.',
|
||||
instructors: ['Mark Sirower'],
|
||||
},
|
||||
'spr3-digital-strategy': {
|
||||
description: 'Digital technologies are playing increasingly important roles in today\'s businesses, markets, economies, and societies worldwide. As digital technologies transform or disrupt organizations\' internal operations and external industry ecosystems, business professionals need to acquire an understanding of the dynamic relationship between technologies and strategies. Such knowledge is critical to effectively leading, participating in, or creating any organization in both private and public sectors. This course is designed to provide a strategic perspective on digital transformation and disruption, and help students develop the knowledge for analyzing, managing, and leveraging digital technologies in diverse settings.',
|
||||
instructors: ['Ning Su'],
|
||||
},
|
||||
'spr3-managing-high-tech': {
|
||||
description: 'We live in an era where "technology" companies are fundamentally changing our lives, and artificial intelligence will further propel this trend. Understanding this mass transformation is crucial, as it\'s evident that each and every industry is being disrupted by technology. Students will study how "management" is conducted in high-tech companies and grasp the distinctions between managing a high-tech company and a traditional one. This course will encompass the megatrends in the technology sector and various real-world business cases. Topics to be covered in this course include (1) Critical success factors of big tech companies; (2) Platform businesses (two-sided business, content platform business); (3) Founding a startup and entrepreneurship (4) Managing innovation; (5) Tech M&As; (6) Culture & People management in the tech industry, and more.\n\nIn addition to U.S. tech companies, we will also discuss Asian companies renowned for their advanced technology applications, such as Baidu, Tencent, Alibaba, and Pinduoduo in China, as well as Kakao and Naver in South Korea. The Professor will also share his experience working as the CEO at Kakao Corp. to help students understand the "CEO Perspective."',
|
||||
instructors: ['Jihoon Rim'],
|
||||
},
|
||||
'spr3-analytics-ml': {
|
||||
description: 'There are a variety of statistical methods, old and new, that are used nowadays to analyze datasets, which can range in size from the small to the enormous. This course gives an introduction to, an overview of and a comparison between these various methods and the attendant terminology and the different kinds of questions they can help answer. Methods that will be discussed include Regression, the Lasso, Discriminant Analysis, Logistic regression, Regression and Classification Trees, etc. This course is not meant for the people doing the analysis but will be geared more towards helping managers understand the material and aid them in having meaningful conversations with the analytics groups in their firms. However, due to its very nature, there will be a mathematical aspect to this course (i.e. there will be formulae), though the aim is to try to understand the intuition behind them.',
|
||||
instructors: ['Rohit Deo'],
|
||||
},
|
||||
|
||||
// === Spring Elective Set 4 ===
|
||||
'spr4-fintech': {
|
||||
description: '"Fintech" is the label for increasingly technological approaches to the main financial intermediation functions: payments, capital raising, remittances, managing uncertainty and risk, market price discovery, and mediating information asymmetry and incentives. In today\'s FinTech businesses, consumers bank via mobile apps integrated into social media, institutions trade electronically, and robo-advisers make decisions about investment portfolios. This inter-departmental course provides an introduction to the emerging FinTech discipline. It is a good starting point for Stern students who may take additional electives in the FinTech area, while also providing an overview of the area for students who intend to take only one FinTech course.',
|
||||
instructors: ['Kathleen DeRose'],
|
||||
},
|
||||
'spr4-sustainability': {
|
||||
description: 'In this course, students will develop an understanding of how leading companies in many sectors are embedding sustainability in their core business strategy and using it to drive innovation; operational efficiency; employee, supplier and customer loyalty; competitive advantage and value to society. They will 1) become familiar with the key environmental and social issues affecting business today, 2) explore the innovations developed by corporate leaders in pursuit of sustainability, 3) become familiar with the latest consumer insight research on sustainability and 4) begin to develop some of the skills required for leading in this new social and political environment (e.g. multi-stakeholder management). This course is multi-disciplinary, and seeks to integrate across the functions of the firm to arrive at an effective firm-wide leadership sensibility.',
|
||||
instructors: ['Alison Taylor'],
|
||||
},
|
||||
'spr4-pricing': {
|
||||
description: 'Price setting is probably the most critical of all marketing mix decisions. It involves an understanding of both supply side factors (e.g. costs) and demand side factors (e.g. consumer willingness to pay). While traditional approaches to pricing theory have revolved around an economic and financial framework, a broader and more pragmatic view entails a comprehensive understanding of the demand side; both at the level of individual customer values, and the more aggregate level of price sensitivities of the market. In this course, we will approach the pricing decision as an intersection of economic, strategic, and behavioral considerations. Using product categories as diverse as healthcare, entertainment, cell phone plans, electronic road pricing, consumer packaged goods, cloud services and durable goods, we will study economic and behavioral approaches to pricing, value-based pricing, price discrimination/customization, bundling, versioning, add-on pricing, subscription pricing, dynamic pricing, durable goods pricing, yield management, freemium pricing, and pricing in two-sided markets.',
|
||||
instructors: ['Kazu Ishihara'],
|
||||
},
|
||||
'spr4-foundations-entrepreneurship': {
|
||||
description: 'This course seeks to explore the many dimensions of new venture creation and growth and to foster innovation and new business formation in independent and corporate settings. The course will integrate both an academic and practitioner view of the challenges facing entrepreneurs and investors involved in entrepreneurial, venture capital and private equity investment activities. The course draws on a variety of disciplines, including management and finance, to develop frameworks and techniques that are needed to plan, start, evaluate and successfully operate ventures.',
|
||||
instructors: ['Glenn Okun'],
|
||||
},
|
||||
|
||||
// === Spring Elective Set 5 ===
|
||||
'spr5-corporate-finance': {
|
||||
description: 'This course helps students develop an analytical framework for understanding how organizations make investment and financing decisions. Students also learn the theory and practice of various valuation techniques. There is an emphasis on understanding the theory and its applications to the real world as well as appreciating the limitations of the tools in practical settings. Specific topics include capital budgeting investment decision rules discounted cash flow valuation real options cost of capital structure dividend policy and valuation methods such as WACC and APV.',
|
||||
instructors: ['Anjolein Schmeits'],
|
||||
},
|
||||
'spr5-consulting-practice': {
|
||||
description: 'The Stern Consulting Corps (SCC): Consulting Practice— Process and Problem Solving is a hands-on experiential learning opportunity that allows students to work in teams to tackle a business issue or opportunity for a client while applying in real time the key steps of the consulting process they are learning in the classroom. Students will discuss their challenges, approaches and recommendations in class and on-line while benefiting from the guidance and expertise of the instructor and a seasoned strategy consulting professional. Whether students are going into the consulting field or another area of business, this course will show you how to break a complicated problem into pieces that can be individually and methodically addressed. We will discuss how to gather the right data to build a relevant fact base which can be used to drive key conclusions. By working on a live SCC project concurrently, you will benefit from the \'flipped classroom model\' to seamlessly integrate knowledge with practice and leave the experience confident in problem solving abilities.',
|
||||
instructors: ['Keshava Dasarathy'],
|
||||
},
|
||||
'spr5-global-strategy': {
|
||||
description: 'This course provides an understanding of the cultural, political, competitive, technological, legal, and ethical environment in which multinational firms operate. It surveys a range of tools and techniques of environmental analysis for use in assessing foreign and global conditions, opportunities, and threats. It also focuses on multinational corporate strategy, organization, and management. Students examine the building of strategic capabilities, collaborating across boundaries, developing coordination and control, and managing activities and tasks, as well as challenges of worldwide functional management, geographic subsidiary management, and top-level headquarters management.',
|
||||
instructors: ['Sinziana Dorobantu'],
|
||||
},
|
||||
'spr5-customer-insights': {
|
||||
description: 'Great insights start with sound research. This course explores the research methods used to understand consumers and their behavior. A solid understanding of these methods enables a critical assessment of the accuracy, relevance, and potential biases in consumer data, leading to more informed strategic decisions. As students collect consumer data firsthand, they develop a deeper appreciation for the theory, rationale, and psychometric principles underlying behavioral science research. This knowledge helps to ensure that insights generated from research are meaningful and align with strategic business goals.',
|
||||
instructors: ['Carol Pluzinski'],
|
||||
},
|
||||
|
||||
// === Summer Elective Set 1 ===
|
||||
'sum1-global-immersion': {
|
||||
description: 'As part of the EMBA Program, students may participate in an optional second-year Global Immersion Experience, in addition to the required first-year GIE. Like the required GIE, this course enables students to gain an understanding of doing business in a given global region through exposure to business leaders and local organizations and institutions. Students travel internationally to a unique global market. In groups, they study the local market and produce a final business idea proposal based on learnings garnered during the GIE.',
|
||||
instructors: [],
|
||||
},
|
||||
'sum1-collaboration': {
|
||||
description: 'Negotiation is the process by which people work together to achieve mutually agreeable outcomes and/or resolve differences. This course provides students with frameworks and concepts for understanding the negotiation process and the opportunity to improve their negotiation and conflict resolution skills. The learning method is experiential. Students engage in a series of negotiation simulations designed to give them experience with a range of different types of negotiations. Emphasis is on principles, strategies and tactics for reaching effective outcomes.',
|
||||
instructors: ['Elizabeth Morrison'],
|
||||
},
|
||||
'sum1-high-stakes': {
|
||||
description: 'As a business leader you are expected to give direction, establish vision and manage career events with well-honed, clear messages and constant communication clarity. Achieving and maintaining this level of excellence is easier said than done. Conquering High Stakes Communications focuses on the tactics and skills you need not only to manage critical conversations, but also to identify them in advance with the objective of maneuvering the ultimate outcome to your advantage. This course quickly reinforces core communication skills and teaches advance communication techniques through intensive drills and immersive scenario-based role-playing sessions. It also personalizes the experience by having students bring "real life" managerial and communication challenges they may currently be facing to the course for assessment and strategy setting. This approach allows students to take what they are learning and immediately put these skills, strategies, and techniques into action at work and outside the classroom.',
|
||||
instructors: ['Steve Mellas', 'Jim Donofrio'],
|
||||
},
|
||||
|
||||
// === Summer Elective Set 2 ===
|
||||
'sum2-innovation-design': {
|
||||
description: 'The pace of disruption today is unlike anything business has faced before. Generative AI is rewriting entire industries in months. Climate imperatives are forcing fundamental redesigns of supply chains, energy systems, and product architectures. Geopolitical fragmentation is redrawing the map of global commerce. In this environment, the companies that hesitate don\'t just fall behind—they become irrelevant.\n\nMost organizations treat disruption as something that happens to them. The best disruptors treat it as something they build—architecting bold moves from a position of strength, before the market forces their hand. Yet most leaders default to what\'s familiar, optimizing yesterday\'s business model rather than designing tomorrow\'s. Successful companies are especially vulnerable: past performance becomes a cognitive trap, making incremental change feel safer than the bold moves the moment demands.\n\nBuilt for senior executives who must drive transformation—not just manage it—Innovation and Design equips you with the frameworks, mindset, and practical tools to identify non-obvious opportunities, challenge entrenched assumptions, and execute disruptive solutions with speed and conviction.',
|
||||
instructors: ['Luke Williams'],
|
||||
},
|
||||
'sum2-social-media': {
|
||||
description: 'This course is designed to provide business leaders with a framework for a company to evaluate social media and enhance their integrated marketing campaigns. You will be provided with the tools to understand the current mobile technology landscape. This course covers important issues that leaders must have a POV on, including: data privacy, marketing technology, mobile video, and top mobile advertising companies. This course strikes a balanced approach of covering the pressing issues of today and timeless foundational marketing principles.\n\nThis is a fast-paced course that is designed for you to learn the basic concepts, terms, and principles that apply to the social media industry. To become familiar with key strategic issues across the sector, you will analyze the activities of the leading social media companies and applications through articles, case studies, and lectures. By the conclusion of the course, as a senior executive you will have gained an understanding of the opportunities and challenges your organization must consider as it manages its social media and mobile technology platforms.',
|
||||
instructors: ['Stewart Krentzman'],
|
||||
},
|
||||
'sum2-leading-ai': {
|
||||
description: 'We\'re at a new age, an age where artificial intelligence is becoming the most influential General Purpose Technology, a technology that once arrived, is poised to morph all aspects of our lives, irreversibly. Artificial Intelligence (AI) rapidly moves into the mainstream, supported by emerging capabilities in cloud and quantum computing, big data, open source software, and ML algorithms to name a few key forces. AI is already demonstrating capabilities that generate greater efficiencies, precision, and personalization, and at times, greater creative output than humans. And with this growing capacity, there grow questions regarding the business value of AI, the societal implications of deploying this technology, and of course, new and intriguing ethical considerations. This course will introduce you to some of the major disruptive Artificial Intelligence developments, concepts, and considerations, and will address the future of work questions as we lead and evolve/sustain AI-enabled businesses.',
|
||||
instructors: ['Anat Lechner'],
|
||||
},
|
||||
'sum2-business-drivers': {
|
||||
description: 'This course covers business drivers of a wide range of industries. Having a perspective about how various industries make money is critical whether you analyze a company for investment, advise its managers, manage its operations, market its products, or choose its capital structure. The course will involve case presentations of various industries and will require active class participation. The course will provide a framework to analyze financial and strategic performance and to identify business drivers.',
|
||||
instructors: ['Dan Gode'],
|
||||
},
|
||||
|
||||
// === Summer Elective Set 3 ===
|
||||
'sum3-valuation': {
|
||||
description: 'This course covers a broad range of issues in corporate financial management. We analyze the core financial decisions made by firms, the investment decision and the financing decision, and examine their impact on the value of the firm in the financial market. Topics that will be covered are: financial planning and forecasting, project analysis and evaluation, resource allocation within firms, valuing flexibility in investment projects, capital structure policy and cost of capital, payout policy, corporate restructurings and firm valuation. A large emphasis will be placed on the application of the concepts and tools developed in the course to financial decisions made by firms through case analysis and real-world examples. By the end of the course, participants should feel comfortable performing a sound analysis of a variety of corporate decisions, and should have developed a thorough understanding of how analyzing strategic and financial decisions from the perspective of value creation can improve managerial decision-making.',
|
||||
instructors: ['Anjolein Schmeits'],
|
||||
},
|
||||
'sum3-entertainment-media': {
|
||||
description: 'This course provides a framework for understanding the key marketing, economic, and strategic issues facing organizations in the entertainment industry. Covers key sectors of the entertainment industry focusing on film, television, home video, cable, music, publishing, sports, and new media. The course utilizes lectures and case studies.',
|
||||
instructors: ['Paul Hardart'],
|
||||
},
|
||||
'sum3-advanced-corporate-strategy': {
|
||||
description: 'This course will tackle advanced corporate strategy, and in particular the execution of corporation strategy (e.g., negotiating prices in acquisitions, integration, etc.). This course will pick up where Strategy 2 leaves off and prepare students for the complex task of actually implementing corporate strategy moves such as mergers, acquisitions, spinoffs, etc.',
|
||||
instructors: ['Michael Dumais'],
|
||||
},
|
||||
'sum3-power-influence': {
|
||||
description: 'This course is designed for individuals interested in learning more about the art and science of influence in organizations. Many people are ambivalent, if not disdainful, of those who seek to wield power and influence at work, but power and influence are key mechanisms by which things get done. For those considering careers in management, it is important to be able to diagnose situations as opportunities to exercise power and influence in order to form and implement new strategies. In addition, managers are usually on the receiving end of these processes. An astute manager knows how to anticipate moves that others will make, how to block or avoid them when they have undesirable consequences, and how to help these moves succeed when their consequences are beneficial.\n\nThe course aims to provide you with "political intelligence" in a sense. After taking this course, you will be able to: (1) diagnose the true distribution of power in organizations, (2) understand your own relationship to power, (3) build your own professional sources of power, and (4) develop influence techniques so that you can resolve conflict more effectively, foster cooperation, and lead change in organizations. These skills will be invaluable throughout your career.',
|
||||
instructors: ['Molly Kern'],
|
||||
},
|
||||
|
||||
// === Fall Elective Set 1 ===
|
||||
'fall1-operations-strategy': {
|
||||
description: 'Operations is concerned with the systematic design, management, and improvement of the processes that transform inputs into finished goods or services. It is one of the primary functions of a firm. As marketing induces demand for products and finance provides the capital, operations produce the product (goods and services).\n\nThis course is intended to provide students a better understanding of how firms can gain competitive advantage from their operations function. Typically, this requires the firm to achieve, at a minimum cost, quality and ecological parity; responsiveness and adaptability to customer needs and desires; rapid time to market; process technology leadership; and sufficient and responsive capacity. The course is designed to develop a problem-solving framework that enables students to undertake managerial and technical analysis of operations that should result in the desired comparative advantage. Unlike many courses, which tend to treat the firm as a "black box", we will be primarily concerned with "opening up" the black box and discovering what makes a firm "tick" - or, for that matter, "stop ticking". Because the operations of a firm vary widely from one industry to the next, a course like this cannot cover all topics that are relevant to any given industry. Rather, I have selected a set of topics that are fundamental to understanding operations in a wide range of industries. These concepts are then illustrated using cases from a diverse set of businesses.',
|
||||
instructors: ['Srikanth Jagabathula'],
|
||||
},
|
||||
'fall1-private-equity': {
|
||||
description: 'This course examines the private equity marketplace. Private equity is a significant source of capital for both new ventures and established firms. Private equity is the investment of capital in private companies to fund growth or in public companies to take them private. Private equity is segregated into several segments of which the principal focus of this class is on the leveraged buyout markets.\n\nThe objective of this course is to provide an overview of the private equity market from the differing perspectives of private equity investors (limited partners), private equity fund sponsors (general partners) and the managers of portfolio companies by focusing on the nature of the market and the strategies employed. The "private equity cycle" will be explored and developed in the course. The private equity cycle includes: (1) Private equity fundraising and structure; (2) Investment origination, valuation, value creation and investment management; (3) Exit strategies.',
|
||||
instructors: ['Bob Semmens'],
|
||||
},
|
||||
'fall1-managing-change': {
|
||||
description: 'Contemporary business environments contain challenges that demand an increasing pace, volume and complexity of organizational change. Most organizations, whether they are entrepreneurial start-ups or long-established Fortune 500 firms, find that they must change or wither. This course is geared toward deepening students\' understanding of the challenges, the techniques, and the burdens associated with initiating and implementing major change in an organization. The course is especially useful for students who plan careers in management consulting, general management (whether in line or staff positions) and entrepreneurship.\n\nThe perspective on change adopted in this course is that competitive advantage today is less a matter of determining the right strategy than of implementing it faster and more smoothly than your competitors. As a result, this course concentrates on process, or how change can be most effectively implemented. In keeping with the emphasis on change process, the course focuses on the exploration and classroom discussion of cases illustrating different change efforts in a variety of organizations across a diverse range of business contexts.',
|
||||
instructors: ['Lynn Gonsor'],
|
||||
},
|
||||
'fall1-social-entrepreneurship': {
|
||||
description: 'The world today is facing multiple intersecting crises—environmental devastation, unprecedented economic inequality, deteriorating health outcomes, racial injustice, amongst others. Social entrepreneurship is the process by which effective, innovative and sustainable business solutions are pioneered to meet these and other social and environmental challenges. It aims to design businesses and leverage the tools of business to create meaningful and measurable impact in the world.\n\nThis course is dedicated to exploring the depths of this rapidly evolving domain. It will explore definition, offer context into how it fits within broader economics and management frameworks, introduce high impact case examples and speakers, offer tools to identify social sector problems and create solutions, explore business designs, and examine methods to measure impact.',
|
||||
instructors: ['Hans Taparia'],
|
||||
},
|
||||
|
||||
// === Fall Elective Set 2 ===
|
||||
'fall2-real-estate': {
|
||||
description: 'This accelerated course focuses on institutional real estate investment, in the United States and globally. It covers real estate property level valuation and risk analysis in the context of a series of case studies. These projects allow graduated skill development over the span of the course, an opportunity to apply financial and property market analysis tools, hone investment presentation skills, and develop skills and techniques useful to effective investment committee decision-makers.\n\nDepending on the level of prior students\' experience, individual members of the presenting "deal team" groups may focus on topics ranging from core market supply/demand forecasting, return sensitivity analysis, debt/equity optimization, and/or sponsor/investor deal structuring features.\n\nWhile primarily relevant to students with an interest in real estate equity and debt investment, the skills and concepts covered in this class are relevant across investment sectors. Prior estate investment experience is not required.',
|
||||
instructors: ['Sam Chandan'],
|
||||
},
|
||||
'fall2-decision-models': {
|
||||
description: 'One of the most crucial skills for a modern manager is knowing how to use data to make decisions. In Decision Models & Analytics, you will learn how to apply modern analytical approaches—including optimization and simulation—to solve complex business problems. Regardless of your career path, the ability to frame, model, and analyze trade-offs in decision-making will make you a more effective decision maker and give you a significant edge.\n\nThis is a hands-on, lab-style course that emphasizes practical modeling. You will work extensively in Excel to build and analyze decision models, learning how to frame and structure complex business problems. The course also explores how Generative AI can assist in constructing and adapting these models—enabling faster model development, easier exploration of alternative approaches, and greater adaptability beyond traditional tools.\n\nThe course is designed to be relevant across a wide range of industries and functional areas, including tech, consulting, finance, government, human resources, operations, and marketing.',
|
||||
instructors: ['Jiawei Zhang'],
|
||||
},
|
||||
'fall2-behavioral-finance': {
|
||||
description: 'What moves market prices for instruments like stocks? Is it fundamentals like earnings, growth, discount rates etc. or something else like psychology & frictions or a combination of both.\n\nFinance theory has long relied on a descriptively sparse model of behavior based on the premise that investors and managers are rational at a collective level and that arbitrage frictions are minimal. In recent years both assumptions have been questioned as the standard model, called the Efficient Market Hypothesis (EMH), fails to account for various aspects of actual fluctuations that appear not to be connected to fundamentals.\n\nBehavioral finance (BF) allows for the condition that investors and managers are not always rational and may make systematic errors of judgment that affect market prices. At the extreme these errors are bubbles or crashes.\n\nWe begin by identifying the respective assumptions of each finance model - EMH and BF - as it relates to three instrument types to varying levels: stocks (65%), cryptocurrencies (fungible coins, 25%) and non-fungible tokens (NFTs, 10%). We then explore practical, real-world examples of these two model assumptions in liquid markets by examining various investing biases and market frictions.',
|
||||
instructors: ['Ian D\'Souza'],
|
||||
},
|
||||
'fall2-crisis-management': {
|
||||
description: 'Effective crisis management is a competitive advantage and a critical attribute of leadership. It isn\'t the nature of the underlying crisis that determines whether a company emerges with its operations, reputation, and financial condition intact, but rather the nature of the response.\n\nThis course focuses on the business decisions, management processes, and leadership skills necessary to anticipate, plan for, manage through, communicate about, and recover from crises affecting corporations and other complex organizations. A key focus of the course is organizational behavior, especially the ways companies in distress and the stakeholders who matter to those companies predictably behave when things go wrong. Another important focus is leadership: how those who lead organizations can maintain the confidence and trust of internal and external stakeholders. The third is strategy: how to navigate a crisis in such a way as to protect long-term business interests and deliver on critical business strategies.\n\nThe course examines examples of effective and ineffective crisis management and topics covered include: case studies in effective and ineffective response, obtaining public forgiveness, thinking strategically in a crisis, crisis communication basics, mistake chains and pattern recognition, taking risks seriously, and neuroscience and crisis.',
|
||||
instructors: ['Helio Fred Garcia'],
|
||||
},
|
||||
|
||||
// === Fall Elective Set 3 ===
|
||||
'fall3-corporate-governance': {
|
||||
description: 'Corporate Governance has evolved into one of the more compelling and challenging subjects in business, law and society. Governance is a multi-faceted and dynamic topic that has socioeconomic, ethical, legal, and regulatory dimensions. The purview of this course is broad, and its intent is to integrate learning from your core EMBA coursework and work experiences to develop interdisciplinary skills to address critical governance issues. These include subjects such as framing the corporate strategy and investment thesis, management leadership and succession planning, undertaking the decision to pursue an initial public offering, pursue a substantial corporate restructuring or engage in a significant acquisition, a business unit sale, or a total entity sale.\n\nWe will also discuss some core business principles—including how to think about capital allocation and understanding key principles of valuation. We will examine the roles and responsibilities (in business theory and in practice) of senior management, directors, shareholders and the corporation\'s other stakeholders—employees, customers, suppliers, and society writ small (the local communities that are touched by the corporation) and writ large (the larger constituent groups—domestic and international)—affected by the corporations\' environmental, social and governance policies, successes and failures.',
|
||||
instructors: ['Sam Liss'],
|
||||
},
|
||||
'fall3-climate-finance': {
|
||||
description: 'Climate change presents one of the central challenges of our generation, with a wide range of effects on financial markets and the broader economy. At the same time, financial markets play an important role in financing the transition to a net-zero economy. In this class, we study the interaction between climate change and firms, financial markets, energy markets, regulators, and policy makers.\n\nGiven that climate change and sustainability issues more generally are affecting nearly every aspect of the corporate, regulatory, and non-profit worlds, the class will be valuable for students with a wide range of backgrounds and career goals, whether they are directly interested in climate and sustainability issues or primarily want to gain a better understanding of how these issues influence more traditional roles in the corporate and financial sector.\n\nThe class is very applied in its outlook but we will frame the analysis through the lens of economic frameworks that help students think through the interactions between climate change and the broader economy in a systematic way. The objective is that the broad selection of topics and guest speakers will provide a variety of complementary perspectives on how climate change will shape economics and finance over the coming decades.',
|
||||
instructors: ['Johannes Stroebel'],
|
||||
},
|
||||
'fall3-emerging-tech': {
|
||||
description: 'This course provides a thorough examination of several key technologies that enable major advances in e-business and other high-tech industries, and explores the new business opportunities that these technologies create. For each of these technologies, it provides an overview of the space corresponding to this class, examines who the major players are, and how they use these technologies. Students then study the underlying technologies; examine the business problems to which they can be applied; and discuss how these problems are solved. Key companies in the spaces created by these technologies are also studied: what these companies do; which technologies they use; how these technologies support their critical applications; and how these companies compete and collaborate among themselves. Moreover, the course examines possible future directions and trends for the technologies being studied; novel applications that they enable; and how high-tech companies can leverage applications of these technologies. This is an advanced course, and it is intended for the students who have already acquired basic knowledge of technical concepts and who want to advance their knowledge of technologies beyond the basics and to further develop an understanding of the dynamics of the spaces associated with these technologies.',
|
||||
instructors: ['Alex Tuzhilin'],
|
||||
},
|
||||
'fall3-tech-innovation-media': {
|
||||
description: 'This course will look to provide a framework for understanding the various technologies impacting the media in the marketplace today - using subjects both ripped from the headlines and grounded in near-term history - as well as provide a structure for assessing the opportunities and challenges of innovations in the 3-5 year time horizon. It is designed to help students become effective marketers in the 21st century. Topics covered will include the digital home, web 2.0, social media, online video, digital advertising, video-on-demand, mobile applications, gaming, sports technologies, and interactive TV.',
|
||||
instructors: ['Jamyn Edis'],
|
||||
},
|
||||
|
||||
// === Fall Elective Set 4 ===
|
||||
'fall4-turnaround': {
|
||||
description: 'This course provides turnaround, restructuring, and distressed investing skills that will expose you to, and prepare you for, careers in the industry. The focus is primarily on corporate reorganizations ranging from small/mid-size businesses to large corporations, including real estate and municipal turnarounds. Topics will include the identification of distressed opportunities; distressed research analysis; and the sourcing of distressed opportunities in the US, Europe, Latin America and Asia. We will also discuss workout strategies; the fundamentals of bankruptcy and the bankruptcy-reorganization process; and career opportunities in the distressed investment business. The class will culminate with students selecting a turnaround investment idea and presenting it to the class for consideration.',
|
||||
instructors: ['Joseph Sarachek'],
|
||||
},
|
||||
'fall4-financial-services': {
|
||||
description: 'The financial services industry touches all of our lives and has been going through a continuous transformation since the deregulation that began in the early 1970\'s. That evolution has accelerated in recent years as more and more pressure has been brought to bear by various stakeholders in the industry who have divergent goals and agendas. Those invested stakeholders include clients, investors, employees, regulators/politicians and the public at large. Overlaying all of this change has been rapid technological advancement that has had a direct impact on how the industry delivers its services, meets expected equity returns and manages the risk inherent in that delivery. This course is an Advanced Finance Elective where THINKING not MEMORIZING is what it is all about.\n\nThis course is a survey course. It provides a broad overview of the financial services industry and of the forces that are continuing to change it worldwide. That change/evolution has resulted in a confederation of sometimes integrated products and services within a multi-product firm. It has also resulted in individual stand-alone businesses within those same integrated financial firms, or in boutique, stand-alone, limited product firms. The course focuses on four big questions: (1) Why and what kind of services are provided by participants in the industry? (2) Who develops, provides and regulates those services? (3) How are they likely to be executed or modified in the future? (4) What skills (both technical and "soft") are required for an individual to succeed in the industry?',
|
||||
instructors: ['James Finch'],
|
||||
},
|
||||
'fall4-game-theory': {
|
||||
description: 'Game theory studies competitive and cooperative behavior in strategic environments, where the fortunes of several players are intertwined. It provides methods for identifying optimal strategies and predicting the outcome of strategic interactions. As well as learning the underlying theory, students learn how game theory can be applied to business.\n\nThis class uses a blend of cases, lectures and simulations to understand real world phenomena through the lens of game theory. For example, we use game theory to answer questions such as: (i) Why did it take so long for Kennedy to respond the way he did in the Cuban Missile Crisis? The course will equip students with game theory techniques for making good business decisions by learning how to recognize and model strategic situations and to predict when and how actions will influence the decisions of others.',
|
||||
instructors: ['Rob Seamans'],
|
||||
},
|
||||
'fall4-brand-strategy': {
|
||||
description: 'Which brands make consumers happy? What attracts consumers to these brands? How do companies create compelling brand experiences? How could you cultivate a brand that fosters customer engagement? This course takes a customer-centric approach to explore such questions with the goal of identifying the ingredients for building and managing inspired brands.',
|
||||
instructors: ['Geeta Menon'],
|
||||
},
|
||||
};
|
||||
@@ -109,6 +109,37 @@ describe('determineStatuses', () => {
|
||||
// Most specs only have 1 qualifying course in spr1 (2.5 credits < 9)
|
||||
expect(statuses['FIN']).toBe('unreachable');
|
||||
});
|
||||
|
||||
it('marks spec as unreachable when infeasible alongside achieved specs due to credit sharing', () => {
|
||||
// Bug scenario: CRF+STR achieved, LCM has 10 credit upper bound but
|
||||
// shared courses (spr3, fall3) are consumed by CRF/STR
|
||||
const selectedCourses = [
|
||||
'spr1-collaboration', // LCM, MGT
|
||||
'spr2-financial-services', // BNK, CRF, FIN, FIM
|
||||
'spr3-mergers-acquisitions', // CRF, FIN, LCM, STR(S1)
|
||||
'spr4-foundations-entrepreneurship', // ENT, MGT, STR(S1)
|
||||
'spr5-corporate-finance', // CRF, FIN
|
||||
'sum1-global-immersion', // GLB
|
||||
'sum2-business-drivers', // STR(S1)
|
||||
'sum3-valuation', // BNK, CRF, FIN, FIM
|
||||
'fall1-managing-change', // LCM, MGT, STR(S2)
|
||||
'fall2-decision-models', // MGT, MTO
|
||||
'fall3-corporate-governance', // LCM, MGT, SBI, STR(S1)
|
||||
'fall4-game-theory', // MGT, STR(S1)
|
||||
];
|
||||
|
||||
// Baseline: LCM is achievable when no specs are achieved (upper bound alone)
|
||||
const statusesBaseline = determineStatuses(selectedCourses, [], []);
|
||||
expect(statusesBaseline['LCM']).toBe('achievable');
|
||||
|
||||
// Core bug scenario: CRF+STR achieved (without MGT), LCM should still be unreachable
|
||||
const statusesWithoutMgt = determineStatuses(selectedCourses, [], ['CRF', 'STR']);
|
||||
expect(statusesWithoutMgt['LCM']).toBe('unreachable');
|
||||
|
||||
// LCM upper bound is 10 (>= 9) but infeasible alongside CRF+STR+MGT
|
||||
const statusesWithMgt = determineStatuses(selectedCourses, [], ['CRF', 'STR', 'MGT']);
|
||||
expect(statusesWithMgt['LCM']).toBe('unreachable');
|
||||
});
|
||||
});
|
||||
|
||||
describe('optimize (integration)', () => {
|
||||
|
||||
@@ -178,7 +178,20 @@ export function determineStatuses(
|
||||
continue;
|
||||
}
|
||||
|
||||
statuses[spec.id] = 'achievable';
|
||||
// Verify spec is actually feasible alongside already-achieved specs,
|
||||
// but only when all course slots are committed (no open sets remain).
|
||||
// With open sets, the user can still pick different courses, so LP
|
||||
// feasibility over selected courses alone would give false negatives.
|
||||
if (openSetSet.size === 0) {
|
||||
const testSet = [...achieved, spec.id];
|
||||
const filteredCourseIds = excludedCourseIds
|
||||
? selectedCourseIds.filter((id) => !excludedCourseIds.has(id))
|
||||
: selectedCourseIds;
|
||||
const feasResult = checkWithS2(filteredCourseIds, testSet);
|
||||
statuses[spec.id] = feasResult.feasible ? 'achievable' : 'unreachable';
|
||||
} else {
|
||||
statuses[spec.id] = 'achievable';
|
||||
}
|
||||
}
|
||||
|
||||
return statuses;
|
||||
|
||||
@@ -6,7 +6,7 @@ import react from '@vitejs/plugin-react'
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
define: {
|
||||
__APP_VERSION__: JSON.stringify('1.1.1'),
|
||||
__APP_VERSION__: JSON.stringify('1.2.1'),
|
||||
__APP_VERSION_DATE__: JSON.stringify('2026-03-27'),
|
||||
},
|
||||
server: {
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
name: emba-course-solver
|
||||
|
||||
services:
|
||||
app:
|
||||
emba-course-solver:
|
||||
build: .
|
||||
container_name: emba-course-solver
|
||||
ports:
|
||||
- "${PORT:-8080}:80"
|
||||
- "8087:80"
|
||||
networks:
|
||||
- default
|
||||
- reverse_proxy
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
reverse_proxy:
|
||||
external: true
|
||||
|
||||
99
docs/superpowers/plans/2026-03-27-achievable-status-fix.md
Normal file
99
docs/superpowers/plans/2026-03-27-achievable-status-fix.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# Achievable Status Fix Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Fix `determineStatuses` so specs that are infeasible alongside achieved specs show as "unreachable" instead of falsely "achievable."
|
||||
|
||||
**Architecture:** Add a feasibility check in `determineStatuses()` after the upper bound check passes. Uses the existing `checkWithS2` helper to LP-solve whether the spec can be achieved alongside the already-achieved set.
|
||||
|
||||
**Tech Stack:** TypeScript, Vitest, javascript-lp-solver
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Write failing test for the bug scenario
|
||||
|
||||
**Files:**
|
||||
- Modify: `app/src/solver/__tests__/optimizer.test.ts`
|
||||
|
||||
- [ ] **Step 1: Add test case for LCM being falsely marked achievable**
|
||||
|
||||
Add to the `determineStatuses` describe block:
|
||||
|
||||
```typescript
|
||||
it('marks spec as unreachable when infeasible alongside achieved specs due to credit sharing', () => {
|
||||
// Bug scenario: CRF+STR achieved, LCM has 10 credit upper bound but
|
||||
// shared courses (spr3, fall3) are consumed by CRF/STR
|
||||
const selectedCourses = [
|
||||
'spr1-collaboration', // LCM, MGT
|
||||
'spr2-financial-services', // BNK, CRF, FIN, FIM
|
||||
'spr3-mergers-acquisitions', // CRF, FIN, LCM, STR(S1)
|
||||
'spr4-foundations-entrepreneurship', // ENT, MGT, STR(S1)
|
||||
'spr5-corporate-finance', // CRF, FIN
|
||||
'sum1-global-immersion', // GLB
|
||||
'sum2-business-drivers', // STR(S1)
|
||||
'sum3-valuation', // BNK, CRF, FIN, FIM
|
||||
'fall1-managing-change', // LCM, MGT, STR(S2)
|
||||
'fall2-decision-models', // MGT, MTO
|
||||
'fall3-corporate-governance', // LCM, MGT, SBI, STR(S1)
|
||||
'fall4-game-theory', // MGT, STR(S1)
|
||||
];
|
||||
const achieved = ['CRF', 'STR', 'MGT'];
|
||||
const statuses = determineStatuses(selectedCourses, [], achieved);
|
||||
|
||||
// LCM upper bound is 10 (>= 9) but infeasible alongside CRF+STR+MGT
|
||||
expect(statuses['LCM']).toBe('unreachable');
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the test to verify it fails**
|
||||
|
||||
Run: `cd /home/bill/dev/emba-course-solver/app && npx vitest run src/solver/__tests__/optimizer.test.ts -t "marks spec as unreachable when infeasible alongside achieved specs"`
|
||||
|
||||
Expected: FAIL — `statuses['LCM']` is `'achievable'` but expected `'unreachable'`
|
||||
|
||||
### Task 2: Implement the feasibility check in determineStatuses
|
||||
|
||||
**Files:**
|
||||
- Modify: `app/src/solver/optimizer.ts:146-185`
|
||||
|
||||
- [ ] **Step 3: Add feasibility check after the upper bound gate**
|
||||
|
||||
In `app/src/solver/optimizer.ts`, in the `determineStatuses` function, replace:
|
||||
|
||||
```typescript
|
||||
statuses[spec.id] = 'achievable';
|
||||
```
|
||||
|
||||
(line 181) with:
|
||||
|
||||
```typescript
|
||||
// Verify spec is actually feasible alongside already-achieved specs
|
||||
const testSet = [...achieved, spec.id];
|
||||
const feasResult = checkWithS2(selectedCourseIds, testSet);
|
||||
statuses[spec.id] = feasResult.feasible ? 'achievable' : 'unreachable';
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run the failing test to verify it now passes**
|
||||
|
||||
Run: `cd /home/bill/dev/emba-course-solver/app && npx vitest run src/solver/__tests__/optimizer.test.ts -t "marks spec as unreachable when infeasible alongside achieved specs"`
|
||||
|
||||
Expected: PASS
|
||||
|
||||
- [ ] **Step 5: Run the full test suite to check for regressions**
|
||||
|
||||
Run: `cd /home/bill/dev/emba-course-solver/app && npx vitest run`
|
||||
|
||||
Expected: All tests pass
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
cd /home/bill/dev/emba-course-solver
|
||||
git add app/src/solver/optimizer.ts app/src/solver/__tests__/optimizer.test.ts
|
||||
git commit -m "fix: mark specs as unreachable when infeasible alongside achieved specs
|
||||
|
||||
determineStatuses() was marking specs as 'achievable' based solely on
|
||||
per-specialization upper bounds, ignoring credit sharing with achieved
|
||||
specs. Now performs an LP feasibility check to verify the spec can
|
||||
actually be achieved alongside the current achieved set."
|
||||
```
|
||||
@@ -0,0 +1,60 @@
|
||||
# Fix: "Achievable" status ignores credit sharing with achieved specs
|
||||
|
||||
**Date:** 2026-03-27
|
||||
**Type:** Bugfix
|
||||
|
||||
## Problem
|
||||
|
||||
`determineStatuses()` marks specializations as "achievable" based solely on per-specialization upper bounds (`computeUpperBounds`), which ignore credit sharing between specializations. A spec can show "achievable" (upper bound >= 9) even when it's infeasible alongside the already-achieved higher-priority specs because shared courses have committed their credits elsewhere.
|
||||
|
||||
**Reproduction scenario:**
|
||||
- 11 courses selected, Fall 1 open
|
||||
- Priority: CRF > STR > LCM > MGT
|
||||
- LCM upper bound = 10 (from spr1-collaboration, spr3-M&A, fall1-managing-change, fall3-corporate-governance)
|
||||
- CRF + STR achieved, consuming credits from spr3 and fall3
|
||||
- LCM shows "Achievable" but `checkFeasibility([all courses], ['CRF', 'STR', 'LCM'])` is infeasible for all S2 choices
|
||||
- Selecting Managing Change for Fall 1 achieves MGT (priority 4) instead of LCM (priority 3)
|
||||
|
||||
## Root Cause
|
||||
|
||||
`determineStatuses()` in `optimizer.ts:146-185` checks only:
|
||||
1. Whether the spec is in the achieved set
|
||||
2. Whether the required course gate passes
|
||||
3. Whether `computeUpperBounds` >= 9 (per-spec, ignoring sharing)
|
||||
|
||||
It never checks whether the spec is actually feasible alongside the achieved set.
|
||||
|
||||
## Fix
|
||||
|
||||
In `determineStatuses()`, after a non-achieved spec passes the upper bound check, add a feasibility check: call `checkWithS2(selectedCourseIds, [...achieved, specId])`. If infeasible, mark as `unreachable` instead of `achievable`.
|
||||
|
||||
### Changes
|
||||
|
||||
**`optimizer.ts` — `determineStatuses` function:**
|
||||
- After the upper bound check passes (currently falls through to `statuses[spec.id] = 'achievable'`), add:
|
||||
```ts
|
||||
const testSet = [...achieved, spec.id];
|
||||
const feasResult = checkWithS2(selectedCourseIds, testSet);
|
||||
if (!feasResult.feasible) {
|
||||
statuses[spec.id] = 'unreachable';
|
||||
continue;
|
||||
}
|
||||
```
|
||||
- No new parameters needed — `selectedCourseIds` is already passed to the function.
|
||||
|
||||
### What doesn't change
|
||||
|
||||
- No new status types — reuses existing `unreachable`
|
||||
- No UI changes — `unreachable` already renders correctly with grey styling
|
||||
- `computeUpperBounds` unchanged — still used for credit bar display
|
||||
- `AllocationResult` type unchanged
|
||||
- `checkWithS2` helper already exists in the same file
|
||||
|
||||
### Test updates
|
||||
|
||||
- Add a test for the bug scenario: given the specific course selection and CRF > STR > LCM > MGT ranking, verify LCM status is `unreachable` (not `achievable`) when CRF and STR are achieved
|
||||
- Existing test `marks achievable when required course is in open set` should be unaffected (uses all-open sets with no achieved specs, so feasibility check passes trivially)
|
||||
|
||||
### Performance
|
||||
|
||||
14 specializations total, at most ~10 non-achieved specs to check. Each check is a small LP solve. Negligible overhead.
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-03-27
|
||||
68
openspec/changes/course-description-popups/design.md
Normal file
68
openspec/changes/course-description-popups/design.md
Normal file
@@ -0,0 +1,68 @@
|
||||
## 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).
|
||||
26
openspec/changes/course-description-popups/proposal.md
Normal file
26
openspec/changes/course-description-popups/proposal.md
Normal file
@@ -0,0 +1,26 @@
|
||||
## Why
|
||||
|
||||
Users selecting elective courses have no way to see course descriptions or instructor information within the app. They must cross-reference a separate PDF document to understand what each course covers. Adding inline course info popups reduces friction and helps users make more informed selections.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add a new data file mapping each course ID to its description text and instructor list, extracted from the J27 Electives PDF and supplementary sources
|
||||
- Add an info icon `(i)` next to each course name in the course selection UI
|
||||
- Clicking/tapping the info icon opens a popover displaying the course description and instructor(s)
|
||||
- Popover closes on click outside, clicking the icon again, or pressing Escape
|
||||
- Works identically on desktop and mobile (no hover/long-press distinction)
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `course-info-popover`: Inline popover UI triggered by an info icon on each course, displaying course description and instructor(s) from a static data source
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
## Impact
|
||||
|
||||
- New file: `app/src/data/courseDescriptions.ts` — static data (~35 course entries with descriptions and instructor arrays)
|
||||
- Modified: `app/src/data/types.ts` — no changes needed (data lives in separate lookup, not on Course type)
|
||||
- Modified: `app/src/components/CourseSelection.tsx` — add info icon and popover component
|
||||
- No solver, state, or worker changes
|
||||
- No new dependencies
|
||||
@@ -0,0 +1,80 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Course description data source
|
||||
The system SHALL maintain a static data file mapping each course ID to its description text and instructor list. Each entry SHALL contain a `description` string and an `instructors` string array. Only courses present in `courses.ts` SHALL have entries.
|
||||
|
||||
#### Scenario: Course with single instructor
|
||||
- **WHEN** looking up course ID `spr2-consumer-behavior`
|
||||
- **THEN** the entry contains a description string and `instructors: ["Radhika Duggal"]`
|
||||
|
||||
#### Scenario: Course with multiple instructors
|
||||
- **WHEN** looking up course ID `spr1-high-stakes`
|
||||
- **THEN** the entry contains `instructors: ["Steve Mellas", "Jim Donofrio"]`
|
||||
|
||||
#### Scenario: Same course name in different sets has per-ID entries
|
||||
- **WHEN** looking up `spr1-collaboration` and `sum1-collaboration`
|
||||
- **THEN** both have description entries, and instructors MAY differ between them
|
||||
|
||||
### Requirement: Info icon display
|
||||
Each course button in the course selection UI SHALL display a clickable info icon next to the course name. The icon SHALL be visible for all non-cancelled, non-disabled courses.
|
||||
|
||||
#### Scenario: Info icon visible on available course
|
||||
- **WHEN** a course is not cancelled and not disabled
|
||||
- **THEN** an info icon is displayed next to the course name
|
||||
|
||||
#### Scenario: Info icon hidden on cancelled course
|
||||
- **WHEN** a course is cancelled
|
||||
- **THEN** no info icon is displayed
|
||||
|
||||
#### Scenario: Info icon hidden on disabled course
|
||||
- **WHEN** a course is disabled (already selected in another set)
|
||||
- **THEN** no info icon is displayed
|
||||
|
||||
### Requirement: Popover opens on info icon click
|
||||
Clicking or tapping the info icon SHALL open a popover displaying the course description and instructor(s). Clicking the info icon SHALL NOT trigger course selection (pin).
|
||||
|
||||
#### Scenario: Open popover on click
|
||||
- **WHEN** user clicks the info icon on a course
|
||||
- **THEN** a popover appears showing the course name, instructor(s), and description
|
||||
- **THEN** the course is NOT pinned/selected
|
||||
|
||||
#### Scenario: Only one popover open at a time
|
||||
- **WHEN** a popover is open and user clicks a different course's info icon
|
||||
- **THEN** the first popover closes and the new one opens
|
||||
|
||||
### Requirement: Popover content layout
|
||||
The popover SHALL display the course name as a heading, instructor(s) as a comma-separated list, and the full description text. If the description exceeds the popover's max height, the content SHALL be scrollable.
|
||||
|
||||
#### Scenario: Display with single instructor
|
||||
- **WHEN** popover opens for a course with one instructor
|
||||
- **THEN** it shows "Instructor: Name"
|
||||
|
||||
#### Scenario: Display with multiple instructors
|
||||
- **WHEN** popover opens for a course with multiple instructors
|
||||
- **THEN** it shows "Instructors: Name1, Name2"
|
||||
|
||||
#### Scenario: Long description scrollable
|
||||
- **WHEN** popover opens for a course with a long description
|
||||
- **THEN** the popover has a max height and the content area is scrollable
|
||||
|
||||
### Requirement: Popover dismissal
|
||||
The popover SHALL close when the user clicks outside it, clicks the info icon again, or presses the Escape key.
|
||||
|
||||
#### Scenario: Close on click outside
|
||||
- **WHEN** a popover is open and user clicks outside of it
|
||||
- **THEN** the popover closes
|
||||
|
||||
#### Scenario: Close on Escape key
|
||||
- **WHEN** a popover is open and user presses Escape
|
||||
- **THEN** the popover closes
|
||||
|
||||
#### Scenario: Close on re-click info icon
|
||||
- **WHEN** a popover is open and user clicks the same info icon
|
||||
- **THEN** the popover closes
|
||||
|
||||
### Requirement: Mobile and desktop parity
|
||||
The popover interaction SHALL work identically on desktop and mobile via click/tap. No hover or long-press interactions are used.
|
||||
|
||||
#### Scenario: Mobile tap opens popover
|
||||
- **WHEN** user taps the info icon on a mobile device
|
||||
- **THEN** the popover opens, same as desktop click behavior
|
||||
21
openspec/changes/course-description-popups/tasks.md
Normal file
21
openspec/changes/course-description-popups/tasks.md
Normal file
@@ -0,0 +1,21 @@
|
||||
## 1. Data Layer
|
||||
|
||||
- [x] 1.1 Create `app/src/data/courseDescriptions.ts` with description and instructors array for each course ID, extracted from the PDF and `ref/inovation-and-design.md`
|
||||
- [x] 1.2 Verify all course IDs in `courses.ts` have corresponding entries (no missing, no extras)
|
||||
|
||||
## 2. Popover Component
|
||||
|
||||
- [x] 2.1 Build a `CourseInfoPopover` component that displays course name, instructor(s), and scrollable description
|
||||
- [x] 2.2 Add dismiss logic: click outside, Escape key, re-click info icon
|
||||
- [x] 2.3 Ensure only one popover is open at a time
|
||||
|
||||
## 3. Info Icon Integration
|
||||
|
||||
- [x] 3.1 Add info icon button next to each course name in `CourseSelection.tsx`
|
||||
- [x] 3.2 Use `stopPropagation` on info icon click to prevent course pinning
|
||||
- [x] 3.3 Hide info icon for cancelled and disabled courses
|
||||
|
||||
## 4. Responsive / Polish
|
||||
|
||||
- [x] 4.1 Handle popover positioning on narrow screens (centered overlay or full-width card on mobile)
|
||||
- [x] 4.2 Verify desktop and mobile parity (click/tap only, no hover/long-press)
|
||||
Reference in New Issue
Block a user