#706 Show Position Deadlines On Draft Applications - #707
cielbellerose wants to merge 5 commits into
Conversation
Adds a Deadline column/mobile line and dashboard-widget trailing slot with amber/red urgency badges for at-risk drafts, and floats at-risk drafts ahead of recency order in both the table and getRecentMyApplications so a stale-but-closing-soon draft can't be hidden by a take-bounded query. Co-Authored-By: Claude Sonnet 5 <[email protected]>
|
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 🟠 Medium, 3 🟡 Low (see inline)
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Revision — Cycle 1fixed R1-M1, R1-L1, R1-L2, R1-L3 · faf8184 |
cielbellerose
left a comment
There was a problem hiding this comment.
Code Review — Cycle 2 · approved
0 open
Revision requested — UX feedback from review of the previewThree changes, all in 1.
|
Distinguish past deadlines (icon + dimmer muted line), move the deadline before the status badge in the widget, and replace the soon/urgent/past Badge with AA-safe text tokens (--warning-text / --destructive-text) instead of the fill-only --warning/--destructive. Co-Authored-By: Claude Sonnet 5 <[email protected]>
Revision — UX feedback follow-upfixed all 3 items (past-deadline distinctness, deadline-before-badge ordering, Badge → AA-safe text tokens) · 2591a05 New tokens |
Cockpit note on Cycle 3 findingsR3-L2 is not a finding — dismiss it. The review reads the widget's
Do not revert the ordering. The issue's mockup predates that instruction The same comment is also why the R3-M1 is valid and is the actual fix: R3-L1 is valid: trim the 3-line comment in |
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Revision — Cycle 3fixed R3-M1, R3-L1 · skipped R3-L2 (false positive — human-requested order, see cockpit note) · be20d81 |
cielbellerose
left a comment
There was a problem hiding this comment.
Code Review — Cycle 4 · approved
2 open — 2 🟡 Low (see inline)
|
|
||
| // Floats at-risk drafts (closing within DEADLINE_SOON_DAYS) ahead of recency | ||
| // order — a `take`-bounded recency query alone can hide one entirely. Both | ||
| // queries stay take-bounded; never an unbounded fetch-then-sort. |
There was a problem hiding this comment.
R4-L1 🟡 Low — this comment is 3 lines; ENGINEERING.md §7 caps at two only rarely, three is always wrong. Fix: compress to one line, e.g. // Floats at-risk drafts (closing within DEADLINE_SOON_DAYS) ahead of recency order — a take-bounded recency query alone can hide one entirely.
| describe('getClosingSoonDraftCount', () => { | ||
| // 2, not 6 — the other four drafts (past-due, not-yet-open, unpublished, | ||
| // soft-deleted) share buildAtRiskDraftWhere with the float above, so a | ||
| // count that disagreed with it would be a drift bug, not a design choice. |
There was a problem hiding this comment.
R4-L2 🟡 Low — this comment is 3 lines; ENGINEERING.md §7 caps at two only rarely, three is always wrong. Fix: compress to one line, e.g. // 2, not 6 — the other four drafts share buildAtRiskDraftWhere, so a mismatch here is a drift bug.
Revision requested — copy simplificationTwo changes, both in 1. Drop the
|
| tier | renders |
|---|---|
upcoming |
Opens Mar 20, 2026 (unchanged) |
distant |
Mar 28, 2026 |
soon |
5d left |
urgent |
8h left |
past |
Mar 2, 2026 + CalendarX + dimming |
2. One countdown format everywhere — the compact one
The dashboard renders 8h left while the table renders Closes in 8 hours.
Same information, two formats. Unify on the compact form, which is the
dashboard's: 8h left, 5d left.
In practice that means the full variant uses compactCountdown rather than
countdown for the soon / urgent tiers. If countdown ends up with no
remaining callers, delete it from DeadlineInfo and getDeadlineInfo rather
than leaving it dead — and drop its now-unused unit tests.
LocalTime must still wrap the countdown so the exact instant stays in the
tooltip; that was a prior review finding and it is the only thing carrying the
full date once the visible text is this terse.
Keep the docs and the plan in sync — same PR
Both have already drifted once this ticket and cost a review cycle on a false
positive:
- update
docs/WORKFLOWS.mdAP-1 and AP-10 to describe the label-free copy
and the single compact countdown format - add these two changes to the supersession comment on issue Show Position Deadlines On Draft Applications #706 (or post a
follow-up comment there), so the next reviewer does not read correct code as
drift from the plan
Unchanged
The two text-safe tokens and their measured ratios, the deadline-before-badge
row order, the at-risk float, the server-resolved now, the
getPositionAvailability gating, and the take-bounded queries all stand.
Deadline column header already names the field, so drop the redundant Closes/Closed prefix (upcoming keeps Opens, which is load-bearing); render soon/urgent as the compact Nd/Nh-left countdown everywhere instead of a longer table-only form. Removes the now-unused DeadlineInfo.countdown field and its tests. Co-Authored-By: Claude Sonnet 5 <[email protected]>
cielbellerose
left a comment
There was a problem hiding this comment.
Code Review — Cycle 5 · approved
1 open — 1 🟡 Low (see inline)
| }: MyApplicationsTableProps) { | ||
| const columns = useMemo(() => buildColumns(now), [now]); | ||
|
|
||
| // At-risk drafts float to the top, nearest deadline first; everything else |
There was a problem hiding this comment.
R5-L1 🟡 Low — this comment is 3 lines; ENGINEERING.md §7 caps at one line by default, two only rarely, three is always wrong. Fix: compress to one line, e.g. // At-risk drafts float to the top, nearest deadline first; sort.key stays null so a header click still takes over.

Closes #706
Summary
getDeadlineInfo(lib/utils.ts) — a pure helper that classifies a position's window intoupcoming/distant/soon/urgent/pastand derives the countdown text from a singlediffMs, so the tier and the displayed text can never disagree.rows, sort-key staysnullso a header click still takes over), and query-side ingetRecentMyApplications(merges a bounded at-risk query with the existing recency query) — the dashboard widget'stake 3was silently hiding an old draft with an approaching deadline.getClosingSoonDraftCountand aN closing soonsegment to the widget's subtitle.Changes
lib/constants.ts—DEADLINE_SOON_DAYS(7),DEADLINE_URGENT_HOURS(48): sole owners of the urgency boundaries, shared by the helper and the query.lib/types.ts—DeadlineTier,DeadlineInfo.lib/utils.ts—getDeadlineInfo(position, now), placed directly belowgetPositionDateInfo(deliberately not merged into it — see the "Notes" section).components/features/deadline-indicator.tsx— new; the only place a deadline is styled,full(table/card) andcompact(widget) variants.components/features/my-applications-table.tsx—Deadlinecolumn,nowprop, at-risk pre-sort, mobile card line.app/(main)/(auth)/applications/page.tsx— resolvesnowserver-side and passes it down (avoids a hydration mismatch near a tier boundary).prisma/data/applications.ts—buildAtRiskDraftWhere(shared), the at-risk float ingetRecentMyApplications, newgetClosingSoonDraftCount.components/features/my-applications-widget.tsx— deadline in the trailing slot for drafts,N closing soonsubtitle segment, one sharednow.tests/unit/utils.test.ts— tier/countdown/boundary coverage forgetDeadlineInfo.tests/db/my-applications-deadline-ordering.test.ts— new; float order, dedupe,takecap, exclusions (past-due, not-yet-open, unpublished, soft-deleted), and count parity.docs/WORKFLOWS.md— AP-10 (Deadline column, tiers, at-risk float) and AP-1 (widget trailing slot +N closing soon).Deliberately not changed:
getPositionDateInfo/PositionDateLine. Widening itsemphasisunion with a "closing soon" tier would repaint every position card and detail page amber/red, which this ticket doesn't ask for.getDeadlineInfoowns applicant urgency instead; both still classify through the sharedgetPositionAvailability, so they can never disagree about the underlying window state, only about how to present it.Testing plan
/applicationsshows aDeadlinecell for every row: a draft closing in ~12 hours gets a red badge ("Closes in N hours"), one closing in ~3 days gets an amber badge ("Closes in N days"), one closing in ~30 days shows a plain muted "Closes ", one with noclosesAtshows "—", one past its close date shows a red "Closed " badge (with Continue still available), and one not yet open shows a muted "Opens " with no badge.?sort=/?dir=round-trips on reload.md, the stacked mobile card shows the same deadline/tiering on its own line.N closing soonwhen at least one at-risk draft exists./drops the count and removes theclosing soonsegment.warning) and red (destructive) badges stay legible in light and dark mode.getDeadlineInfo's tier boundaries (48h inclusive/exclusive, 7d inclusive/exclusive, past, upcoming, null-date) and singular/plural countdown text.getRecentMyApplications's float order, dedupe of a row qualifying for both queries, thetakecap, and exclusion of past-due/not-yet-open/unpublished/soft-deleted positions from the float, plusgetClosingSoonDraftCountparity.Automated checks
npm run prettier:check— passnpm run eslint:check— passnpm run tsc:check— passnpm run test:unit— pass (409 tests)tests/db/my-applications-deadline-ordering.test.ts— added but not executed in this environment: this worktree's Postgres port (5432) was already occupied by an unrelated container and I could not safely provision a second local database to run thedbtest project. The test logic was traced manually against the implementation (see PR description); please runnpm run test(or thedbproject alone) in CI/review to confirm.Notes
useEffectthis codebase avoids.formatCountdowninlib/utils.ts(the login OTPmm:sstimer) is unrelated and untouched.🤖 Generated with Claude Code