Problem
/positions lists open positions alphabetically:
export async function getOpenPositions(): Promise<PositionWithQuestions[]> {
const positions = await prisma.position.findMany({
where: { status: 'open', deletedAt: null },
select: positionWithQuestionsSelect,
orderBy: { title: 'asc' }, // ← alphabetical
});
return positions.filter((p) => isAcceptingApplications(p));
}
Alphabetical order carries no information an applicant needs. The position closing tomorrow is buried between two that close next month.
Wanted order
- Positions with a
closesAt, soonest first.
- Positions without one, at the end, ordered by
opensAt.
- Title as the final tiebreak.
orderBy: [
{ closesAt: { sort: 'asc', nulls: 'last' } },
{ opensAt: { sort: 'desc', nulls: 'last' } },
{ title: 'asc' },
]
Why this is safe here: getOpenPositions filters through isAcceptingApplications, so every row that survives is currently accepting — no past-closed rows and no upcoming ones. Ascending closesAt therefore cannot surface something that closed last week at the top, which is the obvious trap with this change. The .filter() runs after the query and Array.filter preserves order, so the database ordering carries through.
One thing to confirm
Which direction for opensAt? "Sort those at the end by the date that they opened on" doesn't say. I've written desc above — most recently opened first — on the grounds that for a position with no deadline the useful signal is recency, and asc would rank something opened six months ago above one posted yesterday. Flip it if you meant the literal ascending reading.
Non-goals
- No change to
getRecentlyClosedPositions, which feeds the separate recently-closed section of the same page and is correctly ordered closesAt desc — newest-closed first is right there.
- No change to
getOpenPositionsSummary (the dashboard widget) unless you want it consistent; it's a short list where alphabetical is defensible.
- No user-facing sort control.
- No change to
isAcceptingApplications or getPositionAvailability.
Acceptance criteria
Tests
tests/db/ — ordering across a fixture set covering: two close dates, a null close date, two nulls differing by opensAt, and an exact tie falling through to title.
Problem
/positionslists open positions alphabetically:Alphabetical order carries no information an applicant needs. The position closing tomorrow is buried between two that close next month.
Wanted order
closesAt, soonest first.opensAt.Why this is safe here:
getOpenPositionsfilters throughisAcceptingApplications, so every row that survives is currently accepting — no past-closed rows and no upcoming ones. AscendingclosesAttherefore cannot surface something that closed last week at the top, which is the obvious trap with this change. The.filter()runs after the query andArray.filterpreserves order, so the database ordering carries through.One thing to confirm
Which direction for
opensAt? "Sort those at the end by the date that they opened on" doesn't say. I've writtendescabove — most recently opened first — on the grounds that for a position with no deadline the useful signal is recency, andascwould rank something opened six months ago above one posted yesterday. Flip it if you meant the literal ascending reading.Non-goals
getRecentlyClosedPositions, which feeds the separate recently-closed section of the same page and is correctly orderedclosesAt desc— newest-closed first is right there.getOpenPositionsSummary(the dashboard widget) unless you want it consistent; it's a short list where alphabetical is defensible.isAcceptingApplicationsorgetPositionAvailability.Acceptance criteria
opensAtin the agreed direction.npm run prettier:check,eslint:check,tsc:check,testall pass.Tests
tests/db/— ordering across a fixture set covering: two close dates, a null close date, two nulls differing byopensAt, and an exact tie falling through to title.