#369 Make Draft Deletion Recoverable, Preserving Historical Answers - #601
Merged
Conversation
deleteDraftApplication now soft-deletes (deletedAt/deletedById) instead of hard-deleting the row and both answer tables. restoreDraftApplication reuses the same row, so the existing (userId, positionId) unique constraint is never in play. createDraftApplication, createOrUpdateApplicationAnswer and submitApplication each reject a soft-deleted draft with actionable copy instead of writing through it. /my-applications and the apply page surface a Restore action; every reviewer query already excluded soft-deleted rows. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
cielbellerose
commented
Aug 25, 2026
cielbellerose
left a comment
Collaborator
Author
There was a problem hiding this comment.
Code Review — Cycle 1 · approved
0 open — no findings
9 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #369
Summary
deleteDraftApplicationnow soft-deletes (deletedAt/deletedById) instead of hard-deleting theApplicationrow and both answer tables, so an applicant's historical answers survive a delete.restoreDraftApplicationclears the same fields on the same row — no new row is ever inserted, so@@unique([userId, positionId])is never at risk and no migration is needed.createDraftApplication,createOrUpdateApplicationAnswerandsubmitApplicationeach reject a soft-deleted draft with an actionable message instead of silently writing through it or reporting the wrong reason./my-applicationssurfaces a Deleted badge, a muted unlinked title, and a Restore action for a deleted draft;/positions/[id]/applyshows a "You deleted this draft" card with the same Restore control.resolveGlobalAnswerValuespaths that already handle a draft whose questions changed underneath it; a new db suite pins that claim.Changes
prisma/actions/applications.ts— soft-deletedeleteDraftApplication; newrestoreDraftApplication; sharedDRAFT_DELETED_MESSAGE; deleted-row guards increateDraftApplication,createOrUpdateApplicationAnswer,submitApplication.prisma/actions/question-files.ts—readAndWriteAnswerValue's inner application lookup now excludes a soft-deleted draft too.prisma/data/applications.ts—applicationSelectgainsdeletedAt;getMyApplicationssurfaces deleted drafts (and only drafts);getApplicationForApplystops filteringdeletedAt, staying in lockstep withcreateDraftApplication.lib/types.ts—MyApplicationListItemmirrors the newdeletedAtfield.lib/constants.ts—DELETED_APPLICATION_LABEL.components/features/restore-draft-button.tsx— new client leaf, the Restore control shared by the list row and the apply-page card.components/features/my-application-row-actions.tsx— Restore branch for a deleted row; rewritten delete-confirmation copy and toasts.components/features/my-application-primary-action.tsx— no primary action on a deleted row.components/features/my-applications-table.tsx— Deleted badge, muted unlinked title, Status-column sort grouping for deleted rows.components/features/status-badge.tsx— optionaldeletedAtonApplicationStatusBadge, outranking status.app/(main)/(auth)/positions/[id]/apply/page.tsx— "You deleted this draft" card between the closed-window branch and the profile-gate branch.app/(main)/(auth)/my-applications/[id]/page.tsx— threadsdeletedAtthrough toMyApplicationRowActions.tests/db/draft-recovery.test.ts— new: delete/restore lifecycle, write-path guards, reconciliation after restore, and list/reviewer-query visibility.docs/WORKFLOWS.md— rewrote AP-15, added AP-17, updated AP-5/AP-6/AP-9/AP-10.docs/PERMISSIONS.md— updated thedraftrow of the application-lifecycle table and added a bullet on recoverable deletion.Testing plan
/my-applicationswith a Deleted badge, an unlinked title, "—" for Applied, and only a Restore action; the title is not a link;/my-applications/<id>still 404s directly./applicationsor a position card as a manager/admin./positions/<id>/applywhile deleted → "You deleted this draft" card with Restore, View my applications, and Back to position./positions/<id>/apply→ the stepper reopens with every answer, including the uploaded file, present and downloadable.AnswerMismatchNoticeinstead of vanishing.npm run prettier:check && npm run eslint:check && npm run tsc:check && npm run test(db suite requires local Postgres — not runnable in this sandbox;test:unitpasses).Automated checks
npm run prettier:check— passnpm run eslint:check— passnpm run tsc:check— passnpm run test:unit— pass (227/227). Thedbproject, including the newtests/db/draft-recovery.test.tssuite, could not run in this sandbox — the repo'sdocker-compose.ymlbinds host port 5432, which was already occupied by an unrelated container, and noDATABASE_URLwas otherwise configured. CI'srun-testsjob runs the full suite against Postgres.Notes
cleanupOrphanedBlobnever runs on delete./my-applications/[id]) stays a 404 for a deleted draft, matching AP-11's existing soft-delete behavior; widening it would also widendownloadQuestionFileAnswer's shared ownershipwhere.