Split out from #2736 (which covers completed deferred courses).
Problem
~20 accepted participants who filed a Deferral but whose registration still points at a now-Past round, have no certificate, see nothing under 'My Courses'. assignTab (apps/website/src/components/my-courses/useCourseListRow.tsx) returns null for deferred + Past + no-cert, so the row is dropped from every tab.
Deferral doesn't create a new registration. dropoutOrDeferral only writes a dropout row (type Deferral, with newRoundId/oldRoundId).
Prod data on 373 deferrals with a target round:
| outcome |
count |
original registration moved in-place (roundId = newRoundId) |
285 (76%) |
| still on old round (unprocessed / in-flight) |
6 |
| a separate successor registration exists |
2 |
So assignTab bucketing a deferred row by its roundStatus is correct only because the record is normally moved in-place (its roundStatus reflects the new round). It breaks exactly when the in-place move never happens: the stale Deferral marker lingers on a registration still pointing at its old, now-Past round, and the null return hides it entirely.
The old null was justified as "avoid a phantom duplicate of the successor," but that rationale doesn't hold — of the deferred + Past + no-cert rows, 0 have a visible successor.
Two things to decide
- Data / pipeline: why did the in-place round reassignment never run for these ~20 deferrals? The display gap may be masking a stuck deferral-processing step. Investigate before/alongside any display change.
- Product/UX: how should a deferred-but-not-yet-moved, incomplete course display? Showing it in Past Courses would currently render as "completed" (no cert), which is misleading. Needs a dedicated state/label.
Related
Split out from #2736 (which covers completed deferred courses).
Problem
~20 accepted participants who filed a Deferral but whose registration still points at a now-
Pastround, have no certificate, see nothing under 'My Courses'.assignTab(apps/website/src/components/my-courses/useCourseListRow.tsx) returnsnullfor deferred + Past + no-cert, so the row is dropped from every tab.Deferral doesn't create a new registration.
dropoutOrDeferralonly writes adropoutrow (typeDeferral, withnewRoundId/oldRoundId).Prod data on 373 deferrals with a target round:
roundId=newRoundId)So
assignTabbucketing a deferred row by itsroundStatusis correct only because the record is normally moved in-place (itsroundStatusreflects the new round). It breaks exactly when the in-place move never happens: the staleDeferralmarker lingers on a registration still pointing at its old, now-Pastround, and thenullreturn hides it entirely.The old
nullwas justified as "avoid a phantom duplicate of the successor," but that rationale doesn't hold — of the deferred + Past + no-cert rows, 0 have a visible successor.Two things to decide
Related