Skip to content

#740 Show Draft Completion Progress Rings - #756

Merged
b-at-neu merged 3 commits into
devfrom
740-show-draft-completion-progress-rings
Sep 25, 2026
Merged

b-at-neu merged 3 commits into
devfrom
740-show-draft-completion-progress-rings

Conversation

@b-at-neu

Copy link
Copy Markdown
Collaborator

Closes #740

Summary

  • Adds a server-side, per-draft required-question completion aggregate (getApplicationCompletion in prisma/data/applications.ts) that reuses isAnswered and resolveGlobalAnswerValues — the same rules submitApplication and the apply-page stepper already use — so the ring can never disagree with whether Submit is enabled.
  • Adds a new server-only ProgressRing primitive (components/ui/progress-ring.tsx) and renders it on all four surfaces named in the issue: the reviewer drafts table and the merged "All statuses" table (applications-table.tsx), the applicant's own applications table (my-applications-table.tsx), the home dashboard widget (my-applications-widget.tsx), and the position card (position-card.tsx).
  • Only three integers (answeredCount, requiredCount, percent) ever cross to a client — no answer content, no per-question breakdown. Updates the privacy-contract comment on getDraftApplications/DraftApplicationListItem to describe the new sibling aggregate, and rewords the reviewer drafts-view privacy line to disclose that a reviewer can see how far along a draft is.
  • docs/WORKFLOWS.md updated at AP-1, AP-10, AP-17, and PM-8 to describe the ring on each surface and the reworded privacy line.

Changes

  • lib/types.ts — ApplicationCompletion; completion added to MyPositionApplication; updated the DraftApplicationListItem contract comment.
  • lib/utils.ts — calculateAnswerCompletion(questions, values): pure, unit-testable required-question completion count. Zero required → 100% (never NaN); 100%/0% only at exactly full/no answers, otherwise clamped to 1–99 so rounding can never falsely claim "done" or "nothing started."
  • prisma/data/applications.ts — getApplicationCompletion(applications): five flat queries (never per-row) producing Record<applicationId, ApplicationCompletion>; resolves globals through resolveGlobalAnswerValues so a profile-prefilled draft isn't under-reported. Wired into getMyApplicationsByPosition; updated the getDraftApplications contract comment.
  • components/ui/progress-ring.tsx — new. Two-circle SVG (stroke-primary at every value — colour never carries meaning), sized by prop, role="img" with one accessible name ("N% complete"); no 'use client', no hooks.
  • components/features/applications-results.tsx — computes completion for the rows actually rendered (both the drafts-only branch and the merged "All statuses" branch, over the sliced/paginated rows only); reworded the privacy line.
  • components/features/applications-table.tsx — completion prop; a Progress column in the drafts view (no sortAccessor — progress isn't in ApplicationSortField); ring beside the Draft badge in the merged view's desktop and mobile cards. A row with no completion entry renders nothing, never 0%.
  • components/features/applications-table-skeleton.tsx — one extra skeleton bar in the drafts-view header/rows so the new column doesn't shift layout on resolve.
  • app/(main)/(auth)/applications/page.tsx — fetches completion for the caller's own drafts.
  • components/features/my-applications-table.tsx — completion prop; ring beside the Draft badge (desktop + mobile).
  • components/features/my-applications-widget.tsx — a follow-up getApplicationCompletion call over the widget's draft rows only (skipped when there are none); the draft row's — becomes the ring.
  • components/features/position-card.tsx — ring beside Continue application, reading myApplication.completion (now carried on MyPositionApplication).
  • tests/unit/utils.test.ts — calculateAnswerCompletion: none/all answered, rounding + both 1–99 clamps, zero required, file_upload counts as answered, an optional question not affecting the denominator, an empty-string value not counting, and a global answered only through the profile fallback.
  • tests/db/draft-completion.test.ts — new. Scope (one entry per draft in the caller's scope, none outside it), zero-required rendering 100% with no NaN, a required position question moving 0% → 100% once answered, and a required global answered only on the applicant's profile reporting 100%.
  • docs/WORKFLOWS.md — AP-1, AP-10, AP-17, PM-8.

Testing plan

  • As an applicant, start an application on a position with several required questions and answer roughly half — confirm /applications shows the ring beside the Draft badge, the home dashboard widget shows it on that row, and /positions shows it beside Continue application — all three the same number.
  • As a manager of that position, confirm /manage/applications?status=draft shows the same number in the Progress column, and the "All statuses" view shows it beside the Draft badge on the same row.
  • Answer the remaining required questions without submitting — every surface reads 100% and Submit is enabled.
  • Clear one required text answer — the ring drops and Submit blocks.
  • Leave an optional question unanswered with all required ones done — still 100%.
  • With every required global already answered on /profile, start a fresh draft and answer only the position questions — the ring counts the profile-backed globals (the trap this ticket calls out).
  • Start a draft on a position with no required questions — 100%, no NaN, no blank cell.
  • Upload a file for a required file_upload question — it counts as answered; remove it and the count drops.
  • As a manager of position A only, confirm the drafts list still shows no drafts from position B and no percentage appears for them (authorization/scope).
  • Confirm no answer text appears anywhere in the reviewer surfaces — check the page source/RSC payload for a known draft answer string.
  • At 320 / 375 / 768 / 1280px: rings and percentages fit on all four surfaces without clipping or pushing a button off-row.
  • Toggle dark mode — both the track and the arc stay visible.
  • With a screen reader, confirm a ring announces "N% complete" once (not twice).
  • Load /manage/applications?status=draft on a full page of drafts and confirm the query count stays flat (5 extra queries regardless of row count).
  • Empty states: no drafts anywhere on a given surface still renders that surface's existing empty state, not a broken/empty ring.

Automated checks

  • npm run prettier:check — pass
  • npm run eslint:check — pass
  • npm run tsc:check — pass
  • npm run test:unit — 420/420 passing (one pre-existing, unrelated failure: tests/unit/email-delivery-events.test.ts fails to import in this worktree because DATABASE_URL isn't set — confirmed via git diff that this file and its dependencies are untouched by this change)
  • tests/db/draft-completion.test.ts could not be executed in this sandboxed worktree (no isolated Postgres instance available without risking a shared database used by other concurrent work) — added following the same patterns as tests/db/draft-visibility.test.ts and tests/db/application-transitions.test.ts, and reviewed carefully by hand. Please run npm run test locally against a dedicated Postgres instance before merging.

Notes

  • The empty-string edge case resolves in favor of reusing isAnswered rather than forking the rule: the ticket asks that a stored [''] not count as answered, but isAnswered does count it (via partitionAnswerValue) for short/long answers. The UI never produces that row (answer-field.tsx writes [], never ['']), and submitApplication uses the same helper — forking here would make the ring disagree with whether Submit is enabled, which is the worse bug. The unit tests pin []/no-row as unanswered and don't attempt to special-case [''].
  • "One query" is five, and can't be fewer — isAnswered inspects the value against the question's current shape, so no groupBy/_count can produce this number directly. The count is constant in the number of rows (never per-row), which is the property the ticket's cost section actually asks for.
  • #682's status circles (position-stat-circles.tsx) don't exist in this codebase yet, so there was nothing to cross-check against — ProgressRing is the only ui/ circular indicator today.

@b-at-neu b-at-neu added the claude Will be worked on by Claude label Sep 21, 2026
@b-at-neu b-at-neu self-assigned this Sep 21, 2026
@vercel

vercel Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
aplio Ready Ready Preview Sep 25, 2026 7:06pm UTC

@b-at-neu b-at-neu added ready for review PR ready for review agent reviewing Review agent working (in-flight) needs revision Review found issues that need fixing revising Revise agent working (in-flight) and removed ready for review PR ready for review agent reviewing Review agent working (in-flight) needs revision Review found issues that need fixing labels Sep 21, 2026
@b-at-neu

Copy link
Copy Markdown
Collaborator Author

Pipeline Escalation

git rebase origin/dev on 740-show-draft-completion-progress-rings (PR #756) produces conflicts in 5 files. All are semantic, not mechanical — both sides restructured the same components/functions to add different, unrelated features that need to coexist, and a naive pick of either side would silently drop the other's logic. Aborted the rebase; no changes pushed.

components/features/my-applications-table.tsx

  • Ours (origin/dev) added a deadline column (DeadlineIndicator, getDeadlineInfo), an at-risk-draft sort using a now: Date prop, and memoized buildColumns(now) / row sorting.
  • Theirs (this PR, 4d16c09) replaced the now prop with completion: Record<string, ApplicationCompletion>, dropped the deadline column entirely, and rendered a ProgressRing next to the status badge (desktop status cell and the mobileCard renderer) for draft rows.
  • Same conflict shape recurs 3 times in this file: the props interface (now vs completion), the whole column-building logic (memoized buildColumns + at-risk sort vs. inline COLUMNS array with the progress-ring status cell), and the mobileCard renderer (deadline indicator vs. progress ring in the header row).
  • Resolving requires deciding whether the table keeps both now and completion props and renders both the deadline column and the progress ring — a product/design call, not a mechanical merge.

components/features/applications-table.tsx

  • Ours (draft row, status cell): renders ApplicationStatusBadge + ApplicationStatusActions (approve/reject-style actions for a manager viewing a draft).
  • Theirs: renders ApplicationStatusBadge + ProgressRing (completion) for the same draft row, dropping ApplicationStatusActions.
  • A second conflict lower in the file (~line 460-471) follows the same shape (need to diff after resolving the first to confirm scope). Accepting either side outright drops the other's UI element from the same cell.

components/features/my-applications-widget.tsx

  • Ours added getClosingSoonCount / closingSoonCount threaded into buildCountsSummary, plus a now: Date prop passed down to ApplicationList for DeadlineIndicator (compact countdown in the list row).
  • Theirs added getApplicationCompletion and a completion prop passed to ApplicationList, rendering ProgressRing in place of the deadline/date slot for draft rows, and calls buildCountsSummary(counts) without closingSoonCount.
  • Conflicts recur across the import list, the summary/completion computation block, the ApplicationList prop passthrough, and the ApplicationList component's props type + row JSX (deadline countdown vs. progress ring in the same trailing-slot span). Same "which feature wins the slot" ambiguity as above.

app/(main)/(auth)/applications/page.tsx

  • Ours: computes now = new Date() and passes now to MyApplicationsTable.
  • Theirs: fetches getApplicationCompletion(...) and passes completion to MyApplicationsTable.
  • Downstream of the my-applications-table.tsx conflict above — the correct prop set here depends on how that component's interface is resolved.

docs/WORKFLOWS.md (AP-1 See your dashboard)

  • Ours documents the recent-applications widget's trailing slot as the deadline/urgency indicator (with the closing soon subtitle behavior).
  • Theirs documents the same slot as a ProgressRing (required-question completion).
  • Not on the never-touch list, but its correct resolution is downstream of the code conflict above (the doc must describe whatever the merged UI actually does), so it isn't safe to auto-resolve in isolation either.

Why this doesn't fit the auto-resolvable bucket

Every conflict above is "both sides modified the same function body / same lines / same UI slot" — the explicit escalate criteria in the Rebase conflict protocol. origin/dev picked up a deadline/closing-soon-urgency feature for the applications list/widget/table while this PR independently built a draft-completion progress-ring feature for the same rows. Both are real, wanted features; merging them requires a design decision about how the deadline indicator and the progress ring coexist in the same table cell / trailing slot (side-by-side? one replaces the other only for certain rows? does now stay a prop alongside completion?), which is a judgment call for the PR's original author, not something to guess at during a rebase.

No commits were pushed. The branch is unchanged; origin/dev has moved on since this PR's base, so the PR needs the author (or a follow-up planning pass) to reconcile the two features before this can rebase cleanly.

@b-at-neu b-at-neu added needs human Pipeline escalation: 3 review cycles without convergence and removed revising Revise agent working (in-flight) labels Sep 23, 2026
@b-at-neu

Copy link
Copy Markdown
Collaborator Author

Resolution: show the progress ring and the deadline/closing-soon indicator side by side in the shared trailing cell (both features coexist, neither replaces the other).

@b-at-neu b-at-neu added needs revision Review found issues that need fixing revising Revise agent working (in-flight) and removed needs human Pipeline escalation: 3 review cycles without convergence needs revision Review found issues that need fixing labels Sep 23, 2026
@b-at-neu
b-at-neu force-pushed the 740-show-draft-completion-progress-rings branch from 4d16c09 to 90c1c77 Compare September 23, 2026 17:50
@b-at-neu

Copy link
Copy Markdown
Collaborator Author

Revision — rebase resolution

Rebased onto origin/dev per the resolution above: progress ring and deadline/closing-soon indicator now render side by side in the shared trailing slot, neither replacing the other. Both now/getDeadlineInfo (deadline) and completion/getApplicationCompletion (progress ring) props/data flow through unchanged, kept together.

  • app/(main)/(auth)/applications/page.tsx — passes both now and completion to MyApplicationsTable.
  • components/features/my-applications-table.tsx — kept the deadline column (dev) alongside the status-cell ProgressRing (this PR), in both the desktop columns and the mobileCard renderer; buildColumns now takes (now, completion).
  • components/features/applications-table.tsx — draft rows' status cell now shows badge + ProgressRing + ApplicationStatusActions together (desktop and mobile card).
  • components/features/my-applications-widget.tsx — widget fetches both closingSoonCount and completion; each draft/withdrawn row shows the compact DeadlineIndicator and, for drafts with an entry, the ProgressRing beside it in the same trailing slot.
  • docs/WORKFLOWS.md (AP-1, PM-8) — updated prose to describe both elements coexisting in the trailing slot / status cell.

90c1c77

@b-at-neu b-at-neu added ready for review PR ready for review agent reviewing Review agent working (in-flight) and removed revising Revise agent working (in-flight) ready for review PR ready for review agent labels Sep 23, 2026

@b-at-neu b-at-neu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review — Cycle 1 · approved

0 open — clean

@b-at-neu b-at-neu added approved Review passed, ready to merge needs revision Review found issues that need fixing and removed reviewing Review agent working (in-flight) approved Review passed, ready to merge labels Sep 23, 2026
@b-at-neu

Copy link
Copy Markdown
Collaborator Author

Revision — Cycle 3

rebase: docs/WORKFLOWS.md (merged — kept dev's post-#617/#748 AP-1/AP-10 wording, folded in this PR's ProgressRing/trailing-slot additions) · f37a4b9

@b-at-neu b-at-neu added ready for review PR ready for review agent reviewing Review agent working (in-flight) and removed revising Revise agent working (in-flight) ready for review PR ready for review agent labels Sep 25, 2026

@b-at-neu b-at-neu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review — Cycle 4 · approved

0 open — clean (delta-scoped: verified the docs/WORKFLOWS.md rebase merge against current code — Progress column placement, ProgressRing ordering on all four surfaces, draft-row ⋯ removal — no regressions)

@b-at-neu b-at-neu added approved Review passed, ready to merge needs revision Review found issues that need fixing revising Revise agent working (in-flight) and removed reviewing Review agent working (in-flight) approved Review passed, ready to merge needs revision Review found issues that need fixing labels Sep 25, 2026
@b-at-neu
b-at-neu force-pushed the 740-show-draft-completion-progress-rings branch from f37a4b9 to 846c9dd Compare September 25, 2026 14:21
@b-at-neu

Copy link
Copy Markdown
Collaborator Author

Revision — Cycle 4

rebase only (Cycle 4 review already approved, 0 open) · 846c9dd · rebase: components/features/position-card.tsx (dual independent imports, merged both) · rebase: components/features/applications-table-skeleton.tsx (dropped — file deleted by dev's #720 skeleton refactor, unreferenced by any surface; our commit's 2-line edit to it was dead code with no live equivalent to port)

@b-at-neu b-at-neu added ready for review PR ready for review agent and removed revising Revise agent working (in-flight) labels Sep 25, 2026
@b-at-neu b-at-neu added reviewing Review agent working (in-flight) and removed ready for review PR ready for review agent labels Sep 25, 2026

@b-at-neu b-at-neu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review — Cycle 5 · needs revision

1 open — 1 🟠 Medium (off-diff, see below)

R5-M1 🟠 Medium — ApplicationsResultsSkeleton's columns array (isDraftView branch) still has only 4 entries (Applicant/Position/badge/date) — the shape of the drafts table before this PR added Progress. DRAFT_COLUMNS in applications-table.tsx now renders 5 columns (Applicant, Position, Progress, Started, Last updated), so the /manage/applications?status=draft loading skeleton is missing a column and its widths no longer line up with the real table — a layout shift on resolve (ENGINEERING.md §4). The PR's own plan carried a matching fix (an extra bar in the now-deleted applications-table-skeleton.tsx); it wasn't carried over when the last rebase (846c9dd, dropping that file for the shared DataTableSkeleton) landed. Fix: add a Progress column entry to the isDraftView branch (and drop the stray badge shape there, which belongs to the merged view's Status column, not this dedicated drafts view).

const columns: DataTableSkeletonColumn[] = [
...(isDraftView
? []
: ([
{
head: 'w-10',
shape: 'checkbox',
headClassName: 'w-10',
cellClassName: 'w-10',
mobile: 'leading',
},
] satisfies DataTableSkeletonColumn[])),
{ head: 'w-24', cell: 'w-36', subCell: 'w-48', mobile: 'primary' },
{ head: 'w-20', cell: 'w-28' },
{ head: 'w-16', cell: 'w-20', shape: 'badge', mobile: 'trailing' },
{ head: 'w-24', cell: 'w-20' },

@b-at-neu b-at-neu removed the reviewing Review agent working (in-flight) label Sep 25, 2026
@b-at-neu

Copy link
Copy Markdown
Collaborator Author

Pipeline Escalation

This 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): ApplicationsResultsSkeleton in components/features/applications-results.tsx still defines only 4 skeleton columns for the isDraftView branch, matching the drafts table's shape before this PR added a Progress column. DRAFT_COLUMNS in applications-table.tsx now renders 5 real columns (Applicant, Position, Progress, Started, Last updated). This PR's own plan called for a matching skeleton update, but the file that would have carried it (applications-table-skeleton.tsx) was deleted during a rebase (picking up dev's #720 shared-skeleton refactor), and the equivalent update to the new DataTableSkeleton-based skeleton was never carried forward. Net effect: the /manage/applications?status=draft loading skeleton is missing a column and its widths won't line up with the resolved table — a layout shift on resolve.

Context on why this cycled so many times: most of the prior cycles (2–4) were rebase-only dispatches responding to dev outpacing this branch (four separate conflicts total), not new findings — the actual feature code was approved clean at cycle 3/4. This Medium is a real, if narrow, follow-on from the last rebase dropping a skeleton-column update that belonged with it.

Recommendation: straightforward fix — add the missing Progress column placeholder to ApplicationsResultsSkeleton's draft-view branch to match DRAFT_COLUMNS. Not a design disagreement; safe to approve one more revise-agent dispatch outside the automated cap, or fix by hand.

@b-at-neu

Copy link
Copy Markdown
Collaborator Author

Revision — Cycle 5

fixed R5-M1 · 2f5ea22

@b-at-neu b-at-neu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review — Cycle 6 · approved

0 open — clean (delta-scoped: verified R5-M1's fix — Progress column added to the drafts-view loading skeleton with the stray badge shape dropped, matching DRAFT_COLUMNS's 5-column order — no regressions)

b-at-neu and others added 3 commits September 25, 2026 15:01
Adds a server-side aggregate (getApplicationCompletion) that counts
required-question completion for draft applications, reusing isAnswered
and resolveGlobalAnswerValues so the ring can never disagree with
whether Submit is enabled. A new ProgressRing server primitive renders
it on all four surfaces: the reviewer drafts table, the merged
applications table, the applicant's own applications table/widget, and
the position card. Only three integers ever cross to a client; answer
content never does.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Widen ProgressRing's left spacing, reorder the my-applications-widget
draft/withdrawn trailing slot (badge -> ring -> deadline), and drop the
draft-row status-actions menu on the manage applications table.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add missing Progress-column skeleton to the drafts view so its column
count and widths match DRAFT_COLUMNS's 5 real columns.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@b-at-neu

Copy link
Copy Markdown
Collaborator Author

Revision — Cycle 6

3b0a084 · rebase: applications-table.tsx (unioned isAdmin/completion props added by separate merged PRs; kept this PR's later removal of the draft ⋯ menu over dev's older copy), docs/WORKFLOWS.md (kept dev's Force-withdraw region addition to PM-14, applied this PR's draft-menu-removal correction to the Trigger line)

@b-at-neu b-at-neu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review — Cycle 7 · approved

0 open — clean (delta-scoped: rebase onto updated dev; verified no regressions — R5-M1's skeleton fix survived intact, relocated into the shared DataTableSkeleton column defs by the unrelated #720 refactor; force-withdraw (#755) and skeleton-refactor (#720) diffs are base content, not this PR's own changes)

This branch was successfully deployed

1 active deployment
Preview — 3b0a0848 Deployed Sep 25, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Review passed, ready to merge claude Will be worked on by Claude

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Show Draft Completion Progress Rings

1 participant