Plans & Persistence PlannerContext subsystem
A user can maintain multiple independent plans. Each plan is a named slot in localStorage. PlannerContext manages the plan index, active plan pointer, switching, and auto-save. The persistence layer itself (persistence.js) only handles serialization — all logic for when to save lives here.
Multi-plan storage layout
Three localStorage layers hold the multi-plan state. The index and active pointer are small; full plan data (potentially large) lives in per-plan keys.
ncp-plan-data-{id} key. Switching plans triggers an immediate non-debounced save of the current plan before loading the new one.switchPlan sequence
The critical invariant is that the current plan is always saved before the switch, so no edits are lost if the browser crashes or the tab is closed mid-switch.
What captureCurrentPlan() serializes
Every call to captureCurrentPlan() snapshots the current React state into a plain object. This is identical to the export JSON — File → Export and the auto-save write the same structure.
{
placements, // { courseId → semId }
specialTermPl, // { termId → { typeId, semId, duration, company, companyDomain, subline } }
semOrders, // { semId → courseId[] }
shOverrides, // { courseId → number }
bonusSH, // { semId → number }
currentSemId, // string — real-world current semester
offeredOverrides,// manual offered-status overrides
collapsedSubs, // collapsed substitution groups
major, conc, // graduation panel selections
minor1, minor2,
placedOut // string[] — serialized from Set
}
Cohort settings and plan switching
ncp-ent-sem/year and ncp-grad-sem/year are stored as global localStorage keys, but restorePlan() also writes them when loading a plan. This means the "global" cohort settings always reflect the most recently active plan — switching plans updates the cohort to that plan's entry/graduation dates.
Persistence auto-save
A useEffect watching all persisted state calls saveCurrentPlanToSlot() on every change, skipping the first render via an isFirstRender ref. The call is debounced to avoid hammering localStorage on rapid state changes (e.g., drag reordering). The plan object written to localStorage matches the export format exactly.
localStorage key reference
// Written by PlannerContext on plan changes
ncp-plan-index // [{id, name}] — plan list
ncp-active-plan // currently loaded plan ID
ncp-plan-data-{id} // full plan JSON per plan
// Cohort — reflect most-recently-active plan
ncp-ent-sem/year // entry semester and year
ncp-grad-sem/year // graduation semester and year
// Global — not cleared on plan reset
ncp-starred // starred course IDs (global, not per-plan)
ncp-sticky-courses // boolean — courses stay in semester on cohort shift
ncp-zoom // manual zoom level
ncp-theme // theme name string
ncp-collapse-other-credits // boolean (default true) — collapse ≤2 SH section
ncp-show-cont-logo // boolean (default true) — logo on co-op/intern continuation rows
See persistence.js for the full key reference including non-plan keys.