#741 Let Admins Force-Withdraw An Application Without Notifying - #755
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
b-at-neu
left a comment
There was a problem hiding this comment.
Code Review — Cycle 1 · approved
0 open — no findings
b9260ef to
c1090f5
Compare
Revision — Cycle 1rebase-only: resolved conflict with #742's caret restructuring · c1090f5 |
b-at-neu
left a comment
There was a problem hiding this comment.
Code Review — Cycle 2 · needs revision
1 open — 1 🟡 Low (see below)
R2-L1 🟡 Low — Vercel preview deployment failed while run-neon-check is green, so branch-quota exhaustion doesn't explain it. This diff has no schema/migration change and run-prettier-check/run-linting-check/run-tsc-check/run-build-check/run-test-check all pass, so nothing in the changed surface plausibly explains the failure either — worth checking the deploy directly before merging rather than assuming it's noise. https://vercel.com/sga-operational-affairs-projects/aplio/BiTyTdimSfE61fSF2cuhtuMVr6Wy
c1090f5 to
cfabf5a
Compare
Revision — Cycle 2fixed R2-L1 (root cause: branch was 5 merges stale vs |
b-at-neu
left a comment
There was a problem hiding this comment.
Code Review — Cycle 5 · needs revision
1 open — 1 🟠 Medium (see inline)
Pipeline EscalationThis PR has hit the 5-review cycle cap with the latest cycle still producing a Medium finding, so the pipeline is escalating for human attention rather than dispatching another automated revision. Cycle 5 finding (R5-M1, Medium): Context on why this cycled so many times: most of the prior cycles (2–4) were rebase-only dispatches responding to Recommendation: this looks safe to fix directly (either by a human editing the one line, or by approving one more revise-agent dispatch outside the automated cap) — it is not a design disagreement, just a stale doc sentence. |
Admin-only path back to withdrawn from any submitted status, including accepted/rejected. Copies updateApplicationStatus's CAS-transaction shape but skips the after() email dispatch entirely, so that action's "everything here emails" invariant stays unconditional. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
useForceWithdrawApplication mirrors use-application-status-move's shape (pending state, action call, toasts, confirmDialogProps). The status dialog gets a destructive Force withdraw block, admin-only and hidden for draft/withdrawn; the shared status menu gets a matching destructive item below a separator. Both table row and header-actions hosts thread isAdmin down and own their own confirm dialog for the menu entry point, separate from the dialog's own instance. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Per-status matrix over all eight statuses, a manager and the owning applicant both rejected, no EmailLog row on success, and a concurrent double-call resolving to exactly one success and one new event. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PERMISSIONS.md gets the new action's authorization row, the lifecycle table's admin-only force-withdraw note on the unresolved/accepted/ rejected rows, and an amendment to the terminal-withdrawal bullet clarifying the block is applicant-scoped only. WORKFLOWS.md adds AD-13 and touches AP-13, AP-14, PM-14, and XC-9 so none of them still assert a guarantee this ticket deliberately punches a hole in. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rebases past #742's consolidated-caret restructuring; merges the extracted confirmDialog variable with the new force-withdraw confirm dialog, and reconciles the WORKFLOWS.md PM-14 trigger/happy-path text with the force-withdraw addition. Also threads the now-required isAdmin prop through the two draft-status ApplicationStatusActions call sites the rebase didn't touch. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The note!==null caret branch in application-status-header-actions.tsx rendered ApplicationStatusMenu without canForceWithdraw/onForceWithdraw, so admins on the detail page couldn't force-withdraw accepted/rejected applications from the caret (only via Change decision dialog), unlike the table row's identical menu. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The "four unresolved statuses" scoping was stale post-#742; the shipped canForceWithdraw check allows any status except draft/withdrawn. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
b6ead73 to
16770bb
Compare
Revision — Cycle 5fixed R5-M1 · 16770bb |
b-at-neu
left a comment
There was a problem hiding this comment.
Code Review — Cycle 6 · approved
0 open — no findings
Closes #741
Summary
forceWithdrawApplicationserver action that moves an application straight towithdrawnfrom any submitted status — includingaccepted/rejected— without ever sending an email.⋯), admin-only, hidden fordraft/withdrawn, behind aConfirmDialogthat states plainly the applicant is not notified and can edit-and-resubmit.docs/PERMISSIONS.mdanddocs/WORKFLOWS.mdin the same PR, since this deliberately reopens the withdraw → resubmit round-trip thatwithdrawApplicationblocks for the applicant's own action — the docs now describe that hole as an explicit, admin-only, audited product decision rather than leaving them asserting a guarantee the code no longer makes.Changes
prisma/actions/applications.ts— newforceWithdrawApplication:requireAdmin()→ zod →$transaction(scopedfindFirst→ status eligibility → CASupdateMany→applicationStatusEvent.create) → the same tworevalidatePathcallsupdateApplicationStatusmakes. Noafter()dispatch, ever. Also extractsupdateApplicationStatus's inline write-race sentence into the new sharedAPPLICATION_STATUS_CHANGED_MESSAGEconstant (byte-identical).lib/constants.ts—APPLICATION_STATUS_CHANGED_MESSAGEandAPPLICATION_DRAFT_NOT_WITHDRAWABLE_MESSAGE.lib/icons.ts— registersforceWithdraw: CircleSlashinACTION_ICONS.components/features/use-force-withdraw-application.ts— new client hook mirroringuse-application-status-move.ts's shape (pending state, action call, toasts,confirmDialogProps).components/features/application-status-dialog.tsx—isAdminprop; destructive Force withdraw block below Change status, gated the same way as that block (!isNonReviewableApplicationStatus); disables the Select/Apply while either write is pending.components/features/application-status-menu.tsx—canForceWithdraw/onForceWithdrawprops; destructive item below a separator, last in the menu.components/features/application-status-header-actions.tsx,application-status-actions.tsx—isAdminprop; each owns its ownuseForceWithdrawApplication+ConfirmDialogfor the menu entry point, and forwardsisAdmintoApplicationStatusDialog. The table row's two early returns (non-reviewable / terminal-decision) are untouched.components/features/applications-table.tsx,applications-results.tsx,app/(main)/(auth)/manage/applications/[id]/page.tsx— threadisAdmin={user.isAdmin}down from the server.docs/PERMISSIONS.md— new server-action row; lifecycle table gains "admin only: force withdraw" on the unresolved/accepted/rejectedrows; amends the terminal-withdrawal bullet to scope the block to the applicant's ownwithdrawApplicationonly.docs/WORKFLOWS.md— new AD-13 Force-withdraw an application + TOC entry; amendsAP-13,AP-14,PM-14, andXC-9.tests/db/application-transitions.test.ts— newforceWithdrawApplicationsuite.Testing plan
/manage/applications/[id]for anappliedapplication → caret → Force withdraw → confirm. Toast says force-withdrawn and names no email; status badge reads Withdrawn; See more shows a new history rowApplied → Withdrawn · <your name>.reached_out/interview_scheduled/reviewing./emailsand the application's Email history — no new row for any of the above./applicationsshows Withdrawn with Edit & resubmit, and no email arrived. Resubmit → back in the queue as Applied.⋯, or the dialog, at any status./manage/applicationsrow⋯on anappliedrow → Force withdraw works from the table too; the row's menu then disappears (expected until Show The See More Caret On Every Application Status #742 restores the terminal-decision caret).Automated checks
npm run prettier:check— passnpm run eslint:check— passnpm run tsc:check— passnpm run test(unit + db, against a local Postgres) — 783/783 pass, including the newforceWithdrawApplicationsuite (per-status matrix, manager/applicant rejection, noEmailLogrow, concurrent-call CAS race)Notes
ApplicationStatusEventhistory even though it no longer shows inApplication.status.application-status-actions.tsx's two early returns (non-reviewable / terminal-decision rows get no table-row⋯at all) are untouched here. Until Show The See More Caret On Every Application Status #742 restores that caret,accepted/rejectedreach Force withdraw only through the detail page (Change decision → dialog), which still satisfies "available from any submitted status."