context/ State layer
The bridge between UI and logic. Two React Contexts: PlannerContext owns all application state (placements, drag-drop, undo, plans), and ThemeContext manages CSS variable injection. Every UI component reads from here — nothing else.
Children
context (~1,600 lines)
PlannerContext.jsx
All application state: placements, cohort, drag-drop, undo/redo, multi-plan, violations, persistence. The hub of everything.
context (~65 lines)
ThemeContext.jsx
Reads theme from localStorage, injects 100+ CSS variables onto <html>. Cycle between dark/light themes.
How UI components consume context
graph LR
PC["PlannerContext"]
TC["ThemeContext"]
PC -->|"usePlanner()"| H["Header"]
PC -->|"usePlanner()"| BP["BankPanel"]
PC -->|"usePlanner()"| GP["GradPanel"]
PC -->|"usePlanner()"| SR["SemRow / SummerRow"]
PC -->|"usePlanner()"| CC["CourseCard"]
PC -->|"usePlanner()"| IP["InfoPanel"]
TC -->|"useTheme()"| H
TC -->|"useTheme()"| App["App.jsx"]
style PC fill:#fbefff,stroke:#8250df
style TC fill:#fbefff,stroke:#8250df
Every UI component calls usePlanner(). ThemeContext is only read by App.jsx and Header.
The no-prop-drilling contract
No component passes state down via props (except trivial configuration like semId to a row). All shared state comes from usePlanner(). This makes it easy to add features — any component can read or mutate any state without threading props through the tree.
The trade-off: adding new state means editing PlannerContext.jsx, which is already large. When adding state, check first whether it belongs in the context (shared across components) or as local component state (only needed in one place).
Context initialization sequence
sequenceDiagram
participant App
participant TC as ThemeContext
participant PC as PlannerContext
participant CL as courseLoader
participant PS as persistence
App->>TC: mount ThemeProvider
TC->>TC: read ncp-theme from localStorage
TC->>TC: inject CSS variables onto <html>
App->>PC: mount PlannerProvider
PC->>PS: loadSaved()
PS-->>PC: saved plan state (or defaults)
PC->>PC: hydrate placements, cohort, major, etc.
PC->>CL: fetchCourses()
CL-->>PC: Course[] (from all-courses.json)
PC->>PC: normalize → courseMap
PC->>PC: compute derived state (SEMESTERS, violations)
Startup sequence. Theme is injected synchronously; courses load asynchronously.