data/ I/O layer

Four thin I/O adapters. No business logic. Each module wraps one type of external resource: the course catalog JSON, the major/minor requirement JSON files, and browser localStorage. Called exclusively by PlannerContext.

Parent
Called by
PlannerContext.jsx (exclusively)
Rule
I/O only — no business logic, no React

Children

Full data pipeline

sequenceDiagram participant App as App startup participant PS as persistence.js participant CL as courseLoader.js participant LS as localStorage participant FS as public/ (static files) participant ML as majorLoader.js participant GN as graduatenu/ fork App->>PS: loadSaved() PS->>LS: read ncp-plan-index, ncp-active-plan, ncp-plan-data-{id} LS-->>PS: saved JSON or null PS-->>App: hydrated state (or defaults) App->>CL: fetchCourses() CL->>FS: GET /all-courses.json FS-->>CL: 6.5 MB JSON array CL-->>App: Course[] (raw) Note over App: normalizeCourse() called for each Note over App: User selects major in GradPanel App->>ML: loadMajor(path) ML->>GN: GET parsed.initial.json GN-->>ML: Major2 requirement tree ML-->>App: major JSON Note over App: Any state mutation App->>PS: saveState(persist, planObj) PS->>LS: write ncp-plan-data-{id}
Complete data flow from app start through user interaction. All I/O goes through this layer.

External data sources

SourceWhat it isHow updated
public/all-courses.json4,800+ normalized course objects. 6.5 MB. Bundled at build.npm run scrape + npm run patch + commit
graduatenu/packages/api/src/major/1,471 Major2 JSON files. Served as static assets.Scraped from Northeastern catalog. Patched by fix-pooled-requirements.js.
localStoragePlan data, settings, theme. Per-browser.Auto-saved on every state mutation (when persistEnabled=true)
husker.vercel.app/courses/allLive course API (fallback only)Maintained by nu-courses author. Only used if bundled JSON fails to load.