#364 Add An Application Status Transition Graph With Next-Step Quick Actions - #499
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
cielbellerose
left a comment
There was a problem hiding this comment.
Code Review — Cycle 1 · needs revision
4 open — 1 🔴 Critical, 2 🟡 Low, 1 ⚪ Nit
Revision — Cycle 1fixed R1-C1, R1-L1, R1-N1, R1-L2 · 3617070 |
cielbellerose
left a comment
There was a problem hiding this comment.
Code Review — Cycle 2 · needs revision
1 open — 1 🔴 Critical (see inline)
Revision — Cycle 2fixed R2-C1 · e5d7dbf |
cielbellerose
left a comment
There was a problem hiding this comment.
Code Review — Cycle 3 · approved
1 open — 1 🟡 Low (see inline)
cielbellerose
left a comment
There was a problem hiding this comment.
Code Review — Cycle 5 · needs revision
1 open — 1 🔴 Critical (see inline)
Pipeline EscalationPR has reached 5 review cycles without convergence — cockpit cycle-cap reached. Cycle 5 finding still open (Critical):
This is a small, well-understood fix (update one test assertion string), but 5 cycles without landing it suggests either the fix keeps regressing or something about the review/revise loop for this PR needs a human look before continuing automated cycles. |
b463f8e to
f2b44af
Compare
Revision — Cycle 5fixed R5-C1 · f2b44af Also rebased onto |
cielbellerose
left a comment
There was a problem hiding this comment.
Code Review — Cycle 6 · approved
0 open — the R5-C1 stale assertion fix is correct and introduces no regression
Revision requested (human testing)Manual testing found a bug in the status transition flow:
Please either:
|
Pipeline EscalationRebasing The conflictBoth sides restructured the same JSX block — the mobile stacked-card header
Why this isn't safe to auto-resolveThis is a same-lines/same-hunk overlap, not two clearly separate sections — Separately worth flagging: dev's tip already carries What I did
Requested decisionPlease confirm the intended merged structure for the mobile-card header in |
Single source of truth for the six-status pipeline direction, so the server guard and the rendered quick actions can't drift apart. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
updateApplicationStatus and updateApplicationStatuses now scope their writes to the graph's legal source states, so an illegal move fails even from a forged request or a stale tab. Bulk failures and concurrent-change misses now return a user-facing error naming the target instead of throwing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ApplicationStatusActions renders only the legal next moves for the current status — primary/secondary forward buttons, a destructive Reject, and explicit move-back controls — as a roomy panel on the review detail page and a constrained menu in the per-position table. Accept and Reject route through the shared confirm dialog. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Unit tests assert the graph invariants (totality, no self-loops, rejected reachability, source/target inversion). DB tests replace the old "every target reachable from applied" loop with the full 6x6 source x target matrix and add mixed-selection bulk cases. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Scope bulk status updates to forward-only sources so a batch move can't silently walk an already-decided row backward, matching the PR's stated bulk behavior. Also closes the confirm dialog on a failed move and trims two comments to the one/two-line rule. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
getApplicationStatusForwardSources returned [] for 'applied' since no state's forward list ever contains it, making bulk moves to Applied always no-op. Fall back to back-sources when a target has no forward source at all. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds direct unit invariants for getApplicationStatusForwardSources' back-only fallback, per R3-L1. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ixes Reword interview_scheduled to a status label, wire the compact quick- actions menu into the live applications table, and name reachable source statuses in bulk-move error/toast copy. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The bulk "unreachable target" copy computed sources with the full source set instead of the forward-only set that actually scopes the updateMany where-clause, so it could list back-only statuses as valid bulk targets. Match the message to the query and update the stale test assertion. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
getApplicationStatusForwardSources('reviewing') resolves to
[applied, reached_out, interview_scheduled] now that the action uses
forward-only sources; the test still expected the old two-source text.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The bulk-status toast said skipped rows were "still selected", but onApplied only retains ids skipped for a non-reviewable status — rows skipped for a forward-source mismatch (e.g. applied -> interview_scheduled) are deselected, making the claim false. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Revision — Cycle 5Per human manual-testing feedback (not a review finding): the "Interview rebase: |
cielbellerose
left a comment
There was a problem hiding this comment.
Code Review — Cycle 7 · approved
0 open — the skipped-selection copy fix (06531be) is accurate, no regression
Closes #364
Summary
updateApplicationStatusandupdateApplicationStatuses, so an illegal move is impossible even from a forged request or a stale tab.accepted/rejectedstay reversible by a reviewer (naming the explicit target, never a bare "Undo") while remaining terminal for the applicant.Changes
lib/constants.ts—APPLICATION_STATUS_TRANSITIONS(the graph,as const satisfies Record<...>so a new enum member breaks the build),REJECTABLE_APPLICATION_STATUSES,getAllowedApplicationStatusTransitions/isAllowedApplicationStatusTransition/getApplicationStatusSources(the last one derived by inverting the graph),APPLICATION_STATUS_ACTION_LABELS, andTERMINAL_DECISION_STATUS_NOTES.prisma/actions/applications.ts—updateApplicationStatusnow re-checks the freshly-read status against the graph before writing, and scopes theupdateManytogetApplicationStatusSources(target)to close the check-then-write race.updateApplicationStatusesgains the same source scoping; a zero-count result now returns a target-naming{ error }instead of throwing, since a mismatched bulk target is a normal, reachable outcome.components/features/application-status-actions.tsx— new. Renders forward/reject/move-back groups derived from the graph, with acompactvariant (ellipsis +DropdownMenu) for table cells and a roomy variant (stacked buttons) for the detail page. Accept/Reject route through the sharedConfirmDialog, rendered as a sibling of the menu so it survives the menu closing.components/features/application-status-control.tsx— deleted; fully superseded.app/(main)/(auth)/applications/[id]/page.tsx,components/features/position-applications-table.tsx— swapped in the new component; the status cell now shows the badge plus the actions menu.tests/unit/application-transitions.test.ts— new; graph invariants (totality, no self-loops, rejected reachability, source/target inversion).tests/db/application-transitions.test.ts— replaced the "every target reachable from applied" loop with the full source × target matrix, plus bulk mixed-selection cases.tests/db/authorization.test.ts— updated the out-of-scope bulk case to expect the new{ error }return instead of a throw, since it's now indistinguishable from (and superseded by) the graph-legality check.Testing plan
/applications/[id]for anappliedapplication: confirm exactly "Mark reached out", "Move to reviewing", "Reject" appear and no move-back control does.reviewing, click "Accept" → confirm dialog names the applicant; cancel leaves the status unchanged; confirm shows "Moved to Accepted".acceptedapplication: only the terminal note plus "Move back to Reviewing" / "Move back to Interview scheduled" appear — no Accept, no Reject. Click one and confirm the status moves.accepted; in tab B (still showingapplied) click "Move to reviewing" → error toast telling you it's now Accepted and to refresh. Verify the DB status is stillaccepted./applications, select a mix ofappliedandacceptedrows, set status to "Reviewing", Apply → theappliedrows move, theacceptedrows are skipped, and the toast reports both counts.acceptedrows, apply "Reviewing"; then select onlyappliedrows and apply "Accepted" → expect the "None of the selected applications can move to Accepted." error toast.withdrawnapplication's detail page → no controls, existing withdrawn note still shown./my-applications→ still lands back inapplied(unchanged path).Automated checks
npm run prettier:check— passnpm run eslint:check— passnpm run tsc:check— passnpm run test:unit— pass (77/77);npm run test(db project) could not run locally — Postgres wasn't reachable in this environment (npm run db:starthit a port conflict from a preexisting container). CI's db-check job should confirm the new/updated db tests.Notes
REVIEWER_APPLICATION_STATUS_OPTIONSinapplications-bulk-bar.tsx) intentionally still lists all six statuses rather than narrowing to the selection's common legal targets — narrowing would be empty for most mixed selections. The action enforces the graph; the toast (already wired up from Warn Before Bulk Status Changes and Report Skipped Rows #365's skipped-row reporting) explains what happened.tests/db/authorization.test.ts's "every id out of scope" case now asserts a returned{ error }rather than a throw, because the new source-status scoping makes a zero-count bulk result reachable through normal use (a mismatched target), not just through an authorization miss — the decision test in ENGINEERING.md §4 calls for{ error }once that's true.