From 304fbcd0e57380d877a4dd5dcffbd41109d6b81c Mon Sep 17 00:00:00 2001 From: Danial Beg Date: Thu, 1 Oct 2026 14:05:31 -0700 Subject: [PATCH] Stop handing finished applications something new to do MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Topping saved checklists up with missing default tasks reached every entry, including ones already submitted or decided. A school with all eleven tasks ticked gained the new fee-waiver step and read "10 of 11" — telling a student to go and ask about waiving a fee for an application they had already sent. Only applications still being worked on gain tasks now. A finished one keeps the list it finished with. Found by @ZubairQazi reviewing #56, alongside a conflict with #58, which reaches the same conclusion from the aid-application side. This is the half that is already on main. --- web/src/data/applicationTasks.test.ts | 20 +++++++++++++++++++- web/src/data/applicationTasks.ts | 9 +++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/web/src/data/applicationTasks.test.ts b/web/src/data/applicationTasks.test.ts index 2f20471..b059a42 100644 --- a/web/src/data/applicationTasks.test.ts +++ b/web/src/data/applicationTasks.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest' -import { defaultTasksFor, deriveTaskEvents, initialTasksFor, setSharedTask, sharedTaskSummary, tasksForEntry, tasksForRound, updateSharedTask, suggestedWaiverDue, applicationFeeFact, WAIVER_LEAD_DAYS } from './applicationTasks' +import { defaultTasksFor, deriveTaskEvents, initialTasksFor, setSharedTask, sharedTaskSummary, tasksForEntry, tasksForRound, updateSharedTask, suggestedWaiverDue, applicationFeeFact, taskProgress, WAIVER_LEAD_DAYS } from './applicationTasks' import { withKnownCategory, type ApplicationEntry } from './applicationsChecklist' const app = (collegeId: string, fields: Partial = {}): ApplicationEntry => ({ @@ -195,6 +195,24 @@ describe('fee waivers need lead time', () => { expect(topped.find((t) => t.id === 'waiver')?.phase).toBe('before') }) + it('does not hand a submitted school something new to do', () => { + // Found by @ZubairQazi on #56. A school with everything ticked dropped to + // "10 of 11" and was told to ask about waiving a fee for an application it + // had already sent. + const base = app('a', { status: 'submitted', applicationFeeCents: 8500 }) + const finished = defaultTasksFor(base).filter((t) => t.id !== 'waiver').map((t) => ({ ...t, done: true })) + const entry = app('a', { status: 'submitted', applicationFeeCents: 8500, tasks: finished }) + expect(tasksForEntry(entry)).toBe(entry.tasks) + expect(taskProgress(entry).done).toBe(taskProgress(entry).total) + }) + + it('still tops up a school that is still being worked on', () => { + const base = app('a', { status: 'in-progress', applicationFeeCents: 8500 }) + const old = defaultTasksFor(base).filter((t) => t.id !== 'waiver') + expect(tasksForEntry(app('a', { status: 'in-progress', applicationFeeCents: 8500, tasks: old })) + .some((t) => t.id === 'waiver')).toBe(true) + }) + it('leaves a complete list exactly as it was', () => { const entry = app('a', { tasks: defaultTasksFor(app('a')) }) expect(tasksForEntry(entry)).toBe(entry.tasks) diff --git a/web/src/data/applicationTasks.ts b/web/src/data/applicationTasks.ts index e0410c5..eac655f 100644 --- a/web/src/data/applicationTasks.ts +++ b/web/src/data/applicationTasks.ts @@ -26,6 +26,9 @@ const isPrivate = (app: ApplicationEntry) => (app.ownership ?? '').toLowerCase().includes('private') const isEarlyBinding = (app: ApplicationEntry) => app.deadlineType === 'ED' || app.deadlineType === 'REA' +/** Still being worked on, so still worth adding to. */ +const isActive = (app: ApplicationEntry) => + app.status === 'not-started' || app.status === 'in-progress' /** * Whether there is a fee here to waive. @@ -110,9 +113,15 @@ export function tasksForRound(app: ApplicationEntry, deadlineType: ApplicationEn * tasks cannot be deleted (only custom ones can), so a list without one was * saved before that task existed, and a student who added a school last month * should not be the only one without the fee-waiver step. + * + * Only while the application is still being worked on, though. A submitted or + * decided school has nothing left to do, and handing it a new task drops it + * from "all done" to one short — with, in the fee-waiver case, an instruction + * to go and ask about waiving a fee for an application already sent. */ export function tasksForEntry(app: ApplicationEntry): AppTask[] { if (!app.tasks) return defaultTasksFor(app) + if (!isActive(app)) return app.tasks const saved = new Set(app.tasks.filter((t) => !t.custom).map((t) => t.id)) const missing = defaultTasksFor(app).filter((t) => !saved.has(t.id)) if (missing.length === 0) return app.tasks