PlannerContext.jsx Hub

The brain of the entire application. ~1,600 lines of React state, computed values, side effects, and action handlers. Every UI component reads from it via usePlanner(). Understanding this file means understanding how the app works.

Parent
Hook
const {...} = usePlanner()
Imports from core
courseModel, gradRequirements, prereqEval, semGrid, planModel, constants
Imports from data
courseLoader, majorLoader, minorLoader, persistence

Subsystems

State overview

graph LR PC((PlannerContext)) PC --> LD["📦 Loaded Data\ncourses · courseMap · effectiveCourseMap"] PC --> PL["📍 Placement State\nplacements · workPl · internPl\nsemOrders · shOverrides · bonusSH\nplacedOut · substitutions"] PC --> CO["🗓 Cohort Settings\nentSem/Year · gradSem/Year · stickyCourses"] PC --> DV["⚡ Derived / Computed\nSEMESTERS · SEM_INDEX · currentSemId\nprereqViolations · coreqViolations"] PC --> GP["🎓 Graduation Panel\nmajor · conc · minor1 · minor2"] PC --> UI["🖼 UI State\nselectedId · dragInfo · showPanel\nbankSearch · uiScale · isPhone"] PC --> PE["💾 Persistence\nplans · activePlanId\nundoStack · redoStack"] style LD fill:#ddf4ff,stroke:#0969da style PL fill:#dafbe1,stroke:#1a7f37 style CO fill:#fff8c5,stroke:#9a6700 style DV fill:#fbefff,stroke:#8250df style GP fill:#ffd8d3,stroke:#cf222e style UI fill:#f6f8fa,stroke:#656d76 style PE fill:#ddf4ff,stroke:#0969da
Seven state categories. Details for each are in the sections below. Subsystem-specific state (drag refs, SVG lines, plan switching) is documented in the sub-pages above.

Placement state

These are the core data structures that represent where courses live in the plan.

placements
object
{ courseId → semId }. The primary placement map. "CS3500" → "fall2027". Every course-in-semester relationship is encoded here.
workPl
object
{ workId → { semId, duration, company, companyDomain, subline } }. Co-op blocks. duration is 4 or 6 months. 6-month co-ops always span two semester rows. 4-month co-ops only span if placed on a summer semester. company is the display name; companyDomain is a bare domain (e.g. microsoft.com) used to fetch the company logo via Google's S2 favicon service. subline is the user-entered role/title. Any placed co-op grants the EX (Integration Experience) NUPath attribute — see getNuPathCoverage() in gradRequirements.
internPl
object
{ intId → { semId, duration, company, companyDomain, subline } }. Internship blocks. duration is 2 or 4 months. 4-month internships span two rows only if placed on a summer semester. Internships do not grant EX NUPath — co-op only.

Duration spanning — which semesters get occupied

Each block type occupies one or two consecutive semester rows. The grid below shows what happens when a block is dropped on each semester type. Shaded cells are the rows that become occupied.

Block Fall Spring sumA sumB Fall Note
Co-op 4-mo on fall/spring co-op Single row
Co-op 4-mo on summer start cont. Normalizes to sumA; spans sumA→sumB
Co-op 6-mo spring start start cont. Spring → sumA
Co-op 6-mo sumB start start cont. sumB → Fall
Internship 2-mo intern Summer only, single row
Internship 4-mo on fall/spring intern Single row
Internship 4-mo on summer start cont. Normalizes to sumA; spans sumA→sumB

Co-ops and internships are mutually exclusive per semester slot — a co-op blocks internship drops and vice versa. A course cannot be dropped into any semester occupied by either type.

semOrders
object
{ semId → courseId[] }. User-defined ordering within a semester. When a user drags courses to reorder them, this array is updated. Courses not in the array appear at the end.
shOverrides
object
{ courseId → number }. User-set credit hours for variable-credit courses (those with shMin ≠ shMax). Overrides the default course.credits.
bonusSH
object
{ semId → number }. Extra credit hours added to a semester (AP credit, transfer credit applied to a specific term). Shown in the semester header SH count.
placedOut
string[]
CourseIds credited by exam, AP, or transfer without appearing in any semester. Count toward graduation requirements but not in the timeline.
substitutions
array
[{ from, to }]. "ENGW3302 counts as ENGW3315." The effectiveCourseMap applies substitutions so grad requirements see the target course.

Computed/derived state (useMemo)

These values are recomputed automatically when their dependencies change. Never set directly.

SEMESTERS
Semester[]
Ordered semester array for the cohort window. Rebuilt when entry/grad dates change. Each element: { id, label, type, year }.
SEM_INDEX
object
{ semId → ordinal }. Used for "is course A before course B?" comparisons. Built by deriveSemMaps().
currentSemId
string
The real-world current semester (computed from today's date). Used to mark completed vs upcoming semesters in UI.
effectivePlacements
object
Like placements but with substitution targets applied. Used as input to graduation requirement checking.
prereqViolations
Map
courseId → { type: "order"|"unsatisfied", ... }. Computed by running evalPrereqTree() for every placed course. Used to show warning icons and draw red SVG lines.
coreqViolations
Map
Courses whose corequisites are not in the same semester. Computed inline (no separate module).
coopGradConflicts
WorkBlock[]
Co-op blocks whose semester span overlaps the graduation semester. Triggers a warning in Header.

Key actions and setters

Placement mutations (all call pushUndo() first)

onDrop(courseId, semId)
Places a course into a semester. If course was already placed elsewhere, removes it from the old location. Updates placements and semOrders.
onDropBank(courseId)
Returns a course to the unplaced bank. Removes from placements. Does not affect placedOut.
onDropOnCard(dragId, targetId, semId)
Reorders two courses within a semester. Updates semOrders[semId] by swapping positions.
onDropPlacedOut(courseId)
Marks a course as placed-out (test/AP/transfer credit). Removes from placements and adds to placedOut array.

Undo / redo

pushUndo()
Snapshots the current placement state onto undoStack before any mutation. Clears redoStack. The snapshot includes: placements, workPl, internPl, semOrders, shOverrides, bonusSH, placedOut, substitutions.
undo()
Pops from undoStack, pushes current onto redoStack, restores the snapshot. Triggered by Cmd+Z.
redo()
Pops from redoStack, pushes current onto undoStack, restores. Triggered by Cmd+Y / Cmd+Shift+Z.

Multi-plan management

createPlan()
Generates a new UUID plan ID, creates empty plan data, adds to plans index, saves to localStorage, then calls switchPlan(newId).
switchPlan(id)
Saves current plan state to localStorage immediately (non-debounced), loads the target plan's JSON, and rehydrates all placement/cohort/graduation state. See plans/ for the full sequence.
deletePlan(id)
Removes plan from plans index and deletes its localStorage key. If active plan was deleted, switches to the first remaining plan.
exportPlanJSON()
Serializes current state to the plan JSON format and triggers a browser download of plan-name.json.
importPlanJSON(file)
Reads an uploaded JSON file, validates its version field, creates a new plan from it, and switches to it.

Side effects (useEffect)

DependencyEffect
Mount (once)Load localStorage state, fetch courses
selectedId, placements, scrollTickRecompute SVG prerequisite line positions — see svg-lines/
Any persisted state changeDebounced auto-save to localStorage (when persistEnabled) — see plans/
planEntSem/Year, planGradSem/YearRebuild SEMESTERS, optionally remap courses (stickyCourses)
courses loadedBuild courseMap, compute initial violations
Window resizeUpdate isPhone, isMobile, uiScale
Keyboard eventsCmd+Z undo, Cmd+Y redo, Delete remove, Escape close panel