SVG Prerequisite Lines PlannerContext subsystem
Lines connecting course cards are recomputed inside a requestAnimationFrame callback whenever selectedId, placements, showViolLines, substitutions, specialTermPl, or scrollTick changes. Each line is a { from, to, type, fp, tp } object where fp and tp are center-point coordinates in SVG space.
allEdges — computed from courseMap, includes both prereq and coreq edges for all known coursesCoordinate correction
getBoundingClientRect() returns viewport pixels. On desktop the app container has transform: scale(uiScale), so coordinates must be divided by uiScale to convert back to SVG local space. On phone, uiScale is effectively 1 (transform is none), so no correction is applied.
Courses in the "incoming" pseudo-semester are always skipped — no lines are drawn to or from them regardless of mode.
Line types
| type | Color | Condition | Mode |
|---|---|---|---|
prerequisite | Green | A is a prereq of B; A is placed before B — correct order | Selection only |
prerequisite-order | Red | A is a prereq of B but placed in the same semester or after B | Both |
corequisite | Blue | A and B are coreqs; both in the same semester | Selection only |
corequisite-viol | Red | A and B are coreqs but placed in different semesters | Both |
substitution-prereq | Teal | Substituting course fulfills an inherited prereq — correct order | Selection only |
substitution-prereq-order | Red | Substituting course fulfills an inherited prereq but is in the wrong order | Both |
Two drawing modes
The effect runs in one of two modes depending on whether a course is currently selected. Selection mode is a superset of always-on mode — it draws both satisfied and violated lines, while always-on mode draws only red violations.
evalPrereqTree per edge to determine if a "wrong order" violation exists. Selection mode skips this check and draws all edge types unconditionally.scrollTick
Because card positions change when the user scrolls the planner grid, SVG coordinates go stale. A scroll listener on the planner container increments scrollTick by 1. This value is a dependency of the lines effect, so the requestAnimationFrame fires again after scroll and recalculates fresh bounding rects for all visible cards.