Financial Aid: teach the big programs, and flag every school's own aid guarantee - #58
Conversation
The Financial Aid Overview gains a "Know your aid" block above the checklist: the big programs (Pell, QuestBridge, ROTC, the GI Bill), state grants with the student's own state first, the school's own aid application, and the five kinds of scholarships. Each row is colour-coded by where the money comes from and ends with what Edvifi can do about it. More and more colleges promise free tuition below a family income, but only to students who finish that school's aid steps on time. Forty of them, checked against each school's own site, now live on the colleges table, the only source. Deadlines badges them, residency included, and Application Tracking turns each into a task, adding the CSS Profile to public schools whose guarantee runs through it. The migration has to be applied before this deploys: the aid queries select its new columns. Scholarships is renamed My Scholarships. The deadline rail beside each module's Overview now collapses like the dashboard's, sharing its state and its toggle, and scrollbars are thin and quiet instead of the platform default.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Deploying timeline-prototype with
|
| Latest commit: |
3a5215a
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://667e25e2.timeline-prototype.pages.dev |
| Branch Preview URL: | https://feature-financial-aid-overvi.timeline-prototype.pages.dev |
|
Reviewed on the branch — typecheck clean, 529 passing there. The migration is the most carefully sourced thing in this repo: every row with its own URL and a checked date, and the satellite-campus reasoning written down rather than left to be rediscovered. VerifiedI checked all 54 Also worth calling out: the context effect writes from One findingSix schools get the guarantee but not the task that wins it.
The affected rows are the ones you went out of your way to include — the five Ohio State regional campuses (204796, 204662, 204671, 204699, 204705) and Emory's Oxford College — all carrying Buckeye Opportunity / Emory guarantees. The migration's own header says it:
The badge shows; the task does not. Reads as Minor
The conflictMy side is as clean as I can make it now: the One thing to expect on rebase: #57 put Happy to take the rebase off your hands if you would rather not, since three of the four conflicting commits are mine. |
DanialBeg
left a comment
There was a problem hiding this comment.
Two things pinned to lines. The first is the one I would not ship without.
| if (isPrivate(app)) tasks.push(t('css', 'Complete the CSS Profile', 'submit')) | ||
| // Public schools whose guarantee runs through the CSS Profile need it too. | ||
| if (isPrivate(app) || app.aidGuarantee?.needsCss) tasks.push(t('css', 'Complete the CSS Profile', 'submit')) | ||
| tasks.push(t('aid-app', aidAppLabel(app), 'submit')) |
There was a problem hiding this comment.
Six schools get a guarantee here but never this task.
Inside if (fourYear), so a two-year campus never reaches it:
2yr has aid-app: false | 4yr has aid-app: true
2yr tasks: account,fee,transcript,submit,portal,confirm
The affected rows are the ones the migration deliberately includes — the five Ohio State regional campuses (204796, 204662, 204671, 204699, 204705) and Emory's Oxford College — each carrying a Buckeye Opportunity or Emory guarantee. The migration header is explicit that this is the point:
Every one of them depends on the student finishing that school's aid steps on time, so the app shows the guarantee next to the school and turns it into a task
The badge shows; the task does not. if (fourYear || app.aidGuarantee) covers it, and covers the next two-year school to announce one. None of the six has needsCss: true, so the CSS branch above is unaffected.
There was a problem hiding this comment.
Good catch, fixed in 3a5215a: the task now goes to fourYear || app.aidGuarantee, with a test for a two-year campus that has a guarantee.
| .map((a) => a.collegeId) | ||
| if (due.length === 0) return | ||
| for (const id of due) { guaranteeChecked.current.add(id); guaranteeInFlight.current.add(id) } | ||
| fetchAidColleges(due) |
There was a problem hiding this comment.
Worth adding to the deploy warning at the top of the PR: this runs for every student on every dashboard load, not only inside Financial Aid. ApplicationsContext wraps the whole dashboard, and fetchAidColleges selects the aid_guarantee* columns.
The .catch means it degrades rather than breaks — guarantees never get copied, the aid task reads generically, nothing throws. So the blast radius of a pre-migration deploy is wider than the description says but not worse in kind. Mentioning it because "Financial Aid breaks" sets the expectation that the dashboard is fine, and someone debugging a failing colleges query from the dashboard would be looking in the wrong module.
The retry is sound, incidentally: a failure clears the id from guaranteeChecked, and the effect re-runs on the next change to the list rather than spinning.
There was a problem hiding this comment.
Agreed. Added to the deploy warning in the description, along with #57's migration, since the shared search columns now need it too.
| if (!found.has(a.collegeId) && !none.has(a.collegeId)) return a | ||
| const g = found.get(a.collegeId) | ||
| const copy = g ? { headline: g.headline, ...(g.residents ? { residents: g.residents } : {}), needsCss: g.needsCss } : null | ||
| if (JSON.stringify(copy) === JSON.stringify(a.aidGuarantee)) return a |
There was a problem hiding this comment.
Minor, and only a trap for later: this is key-order dependent. It is correct today because both sides are built by the expression two lines up, so the keys always come out in the same order — including the conditional residents, which is absent on both sides or present on both.
Reorder that literal in a future edit, though, and every entry compares unequal on every load, so the whole college list gets rewritten each time the dashboard opens. Comparing the three fields directly would not care.
There was a problem hiding this comment.
Fixed in 3a5215a: it now compares headline, residents and needsCss directly.
Merges #56, #57 and #59. tasksForEntry keeps main's version (any missing default task, in its own phase, only while the application is still being worked on) and adds the aid task's label refresh and the per-entry cache. From review on #58: the aid-application task now goes to any school with a guarantee, not just four-year ones, so Ohio State's regional campuses and Emory's Oxford College get the task that wins theirs. The guarantee copy is compared field by field rather than as JSON, which depended on key order.
DanialBeg
left a comment
There was a problem hiding this comment.
Approving. All three findings addressed, and the merge is the combined version you proposed.
Verified since:
if (fourYear || app.aidGuarantee)— the six guarantee campuses get the task nowsameGuarantee()replaces theJSON.stringifycompare- the deploy warning covers the dashboard path, and names the
20261001fee migration as a second prerequisite — which I had missed on my own PR computeTaskslands where you said it should: any missing default spliced into its own phase, inside theisActiveguard, with the label refresh and the cache kept- 543 passing, typecheck clean, and
b5ecf13is in its history so it is merging against current main
I also read AidGuideSections.tsx, which I had skipped. Nothing to flag: every external link carries rel="noopener noreferrer", the SVGs are aria-hidden, the collapsibles have aria-expanded, and the guarantee fetch is lazy with a cancellation guard and a real 'failed' state that retries on reopen rather than an empty list pretending to be an answer.
The migration is already applied. I pushed 20260930000000_college_aid_guarantees to the remote yesterday so the ordering your description asks for is satisfied before the merge rather than after. 54 schools carry a guarantee; spot-checked Harvard, Michigan, Berkeley and Ohio State against their rows. It needed --include-all, since 20260930 sorts before the already-applied 20261001; I checked the two are disjoint first — zero references to the fee columns — so applying out of order lands the same state.
So nothing stands between this and merging.
One apology. You will have seen this PR close and reopen yesterday with no explanation: I tried to merge it without noticing it was a draft, and the branch deletion I had chained with ; ran anyway when the merge was refused, which closed it. Restored at the same SHA within a minute and nothing was lost, but that was careless with someone else's branch and I am sorry for the noise.
Apply the migrations before deploying
Two migrations must be on the remote project before this deploys:
20260930000000_college_aid_guarantees(this PR)20261001000000_college_application_fees(Store what a school charges to apply, and whether it waives it #57, already on main)Financial Aid's college queries select the new
aid_guarantee*columns, and since #57 theshared college search columns include the fee columns too. Deploying first makes those
queries fail. In Financial Aid, Overview shows "Couldn't check your schools", Deadlines and
Aid Compare show their failed state, and aid college search returns nothing. This is the
same failure #42 found with
student_body.It also reaches the dashboard, not just Financial Aid:
ApplicationsContextwraps thewhole dashboard and copies each saved school's guarantee on every load. That path degrades
rather than breaks, but expect a failing
collegesquery from there too. If you aredebugging one, don't only look in the Financial Aid module.
Until both are applied, a local dev server pointed at the remote project shows these same
failure states. That is expected.
What changed
Financial Aid Overview
A Know your aid block above the checklist: four collapsible rows in the checklist's own
shape, closed by default and color-coded by where the money comes from.
Each row ends with an Edvifi can… strip (logo, one line, a button) that opens the tool
that does the job: FAFSA prep, Aid Compare, Deadlines, Aid Engine.
Guarantees everywhere a school appears
Guarantee · Michigan residents"). Opening any four-year school shows its guarantee and
what to file, or a reminder to check its own aid application.
application", named after its guarantee. Public schools whose guarantee needs the CSS
Profile (Michigan, UNC, UVA, Georgia Tech) also get the CSS task, carrying over the tick
if the profile is already filed for another school. Lists saved before this change gain
both tasks while the application is still in progress; submitted or decided ones are
left alone.
Rename
Scholarships becomes My Scholarships (tab, heading, guided tour, checklist copy).
Collapsible Overview rail in every module
The deadline rail beside a module's Overview (Financial Aid, Application Tracking, Essays) now
collapses like the dashboard's: same column, divider and edge toggle (one shared component).
It shares the dashboard's open/closed state. On phones it stacks under the checklist and
always shows.
Scrollbars
Thin, quiet scrollbars app-wide instead of the platform default. The page-level scroll areas
next to the rail show their thumb only on hover.
The guarantee data
The
collegestable is the only source: Deadlines, the Overview and the full list readit directly, and each saved school keeps a short copy (
ApplicationEntry.aidGuarantee) sotasks can be built without a lookup. That copy is refreshed once per school per session, so
a later correction reaches lists saved before it. To change a guarantee, write a new
migration.
No public dataset carries these (not the Scorecard, not the Common Data Set). Each of the 40
was read off the school's own site, re-verified page by page, and stamped 2026-09-30. CSS
Profile requirements come from College Board's 2027–28 participating-institutions list.
Where a threshold changes for students entering in fall 2027 (UChicago, Vanderbilt, Rice,
W&L), the row carries the fall 2027 figure, because that is this year's seniors.
Northwestern's $150K figure is a fundraising goal, not a promise, so it is listed by its
current commitment (pay nothing under $70K).
The migration adds
aid_guarantee,_residents,_needs_css,_detail,_action,_urland_checked_on, with a CHECK that the four text fields are set together. Itbackfills 54 rows keyed on
scorecard_id. Campuses a program covers are in (all six OhioState campuses, Emory Oxford). Graduate and online rows that share a parent's domain are
out (Teachers College, Weill Cornell, TAMU Health Science Center, UF Online). It was applied
twice against a local Postgres to confirm it is clean and re-runnable, and the CHECK refuses
a partial update.
Reviewed
Three code-review passes. Fixes that came out of them include:
tsc, eslint, 543 tests and the production build all pass.
Not in scope
there before this change.