#706 Show Position Deadlines On Draft Applications - #707
cielbellerose wants to merge 9 commits into
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 🟠 Medium, 3 🟡 Low (see inline)
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.
|
Revision — UX feedback follow-upfixed all 3 items (past-deadline distinctness, deadline-before-badge ordering, Badge → AA-safe text tokens) · 2591a05 New tokens |
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 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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 <noreply@anthropic.com>
Restore the widget's title-badge-trailing order and add the draft deadline segment to the application detail page per the addendum. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
db45223 to
298d505
Compare
Closes out three still-open review threads from earlier cycles (R4-L1, R4-L2, R5-L1) that were never trimmed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Revision — Cycle 1 (Human Direction)fixed rebase-authorized action-cell merge, widget row order regression, detail-page deadline segment (scope addendum) · fixed R4-L1, R4-L2, R5-L1 (leftover long comments) · rebase: components/features/my-applications-table.tsx (combined per authorized resolution — kept buildColumns structure, wrapped action cell in TERMINAL_DECISION_STATUSES guard) · 298d505, db005d0 follow-up: no automated render-test coverage added for the detail page's draft/non-draft split — this repo has no component-render test infra (no testing-library dependency), so the split relies on the already-unit-tested |
cielbellerose
left a comment
There was a problem hiding this comment.
Code Review — Cycle 6 · approved
0 open
cielbellerose
left a comment
There was a problem hiding this comment.
Human Direction — Cycle 2
Operator-authored, not a review-agent pass. Two UI corrections, both recorded in the #706 body under "Scope addendum 2". Re-read that issue body before starting.
1. Move the dates to the left of the status tag in the dashboard widget
components/features/my-applications-widget.tsx — the row currently renders:
Link (title) → <ApplicationStatusBadge> → <span> (DeadlineIndicator | LocalTime)
Swap the last two so the trailing date content sits before the badge and the badge is the rightmost element:
Link (title) → <span> (DeadlineIndicator | LocalTime) → <ApplicationStatusBadge>
This applies to all three trailing contents — the urgency countdown (5h left), the plain close date, and the submitted date.
This reverses item 2 of the previous Human Direction, which asked for the badge to sit immediately after the title. That earlier instruction is superseded; the operator has seen it rendered and wants the dates on the left. Update the widget mock in the issue's section 2 and the AP-1 entry in docs/WORKFLOWS.md so neither keeps describing the old order.
2. Remove the orphaned separator dot on the past-deadline line
components/features/deadline-indicator.tsx, in mutedLine:
{isPast && '· '}Delete this expression. It renders a leading dot with nothing to its left — 🗓 · Mar 2, 2026.
For context, so the fix is made with the right intent: this is a refactor leftover, not a deliberate treatment. The line originally read {info.label}{isPast ? ' · ' : ' '}, where the · separated the word Closed from the date. Commit 2797540 ("drop deadline label, unify countdown to compact form") removed {info.label} as redundant with the column header but left the separator behind.
Keep isPast && 'opacity-70' — the dimming is the intended past-tier treatment. Do not substitute another separator or prefix; the bare icon-plus-date line is what is wanted. upcoming keeps its Opens prefix unchanged, and the emphasized soon/urgent/past lines are untouched.
Note mutedLine is shared by both variants, so this also fixes the table's full variant on non-draft rows with a closed position — verify both surfaces.
Scope
These two changes only. No other behaviour, tiering, ordering or data change. The full acceptance criteria, including the four added ones, are in the #706 body.
Move widget trailing slot before status badge and drop the orphaned separator dot on the past-deadline muted line. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Revision — Cycle 2fixed both Human Direction items · cae2a5f |
cielbellerose
left a comment
There was a problem hiding this comment.
Code Review — Cycle 7 · approved
0 open
cielbellerose
left a comment
There was a problem hiding this comment.
Human Direction — Cycle 3
Operator-authored, not a review-agent pass. One behavioural change, recorded in the #706 body under "Scope addendum 3". Re-read that issue body before starting.
Treat withdrawn exactly like draft for deadline urgency
Every urgency gate in this PR currently tests status === 'draft'. That is too narrow. components/features/my-application-primary-action.tsx renders Edit & resubmit for a withdrawn application while the position is still accepting — its own comment calls draft and withdrawn "the only applicant-editable statuses". The close date is therefore just as actionable on a withdrawn application, and it can expire just as silently.
Widen each gate to draft-or-withdrawn:
components/features/my-applications-widget.tsx— the row branchesapp.status === 'draft' ? <DeadlineIndicator …/> : <LocalTime date={app.submittedAt} …/>. Withdrawn takes theDeadlineIndicatorbranch.components/features/my-applications-table.tsx—emphasizeUrgency={a.status === 'draft'}on the deadline column, and theatRiskDeadlineDatehelper'sif (a.status !== 'draft') return nullguard. Both widen. The mobile card inherits the cell, so it needs no separate change — verify that.app/(main)/(auth)/applications/[id]/page.tsx— the deadline segment is gated onisDraft. Widen only the deadline gate.isDraftalso drives theDraft · last savedvsAppliedprefix, which must NOT change — a withdrawn application keepsApplied <date>. Introduce a separate boolean for the urgency gate instead of reusingisDraft.prisma/data/applications.ts—buildAtRiskDraftWherehasstatus: 'draft'; make itstatus: { in: ['draft', 'withdrawn'] }. This flows to bothgetRecentMyApplications's float andgetClosingSoonDraftCount.
Ordering and the count are in scope, not just styling. A withdrawn application closing in two days that never floats into the widget's top 3 gets nothing from the display change, and a N closing soon subtitle that omits it contradicts the badge rendered right above it. Same reasoning the original scope gives for the draft float.
Rename the two now-misleading helpers — neither is draft-specific any more: buildAtRiskDraftWhere → buildAtRiskWhere, getClosingSoonDraftCount → getClosingSoonCount. Update every call site and tests/db/my-applications-deadline-ordering.test.ts.
Unchanged: submitted, reviewing, accepted, rejected still render the plain muted date with no urgency styling and never float. Tiering, thresholds, copy, row order and the DeadlineIndicator component's internals are all untouched.
Tests and docs
Extend tests/unit/utils.test.ts and tests/db/my-applications-deadline-ordering.test.ts so a withdrawn application is covered at the tier boundaries and in the float ordering — the DB test in particular should prove a withdrawn at-risk row floats and is counted. Update docs/WORKFLOWS.md (AP-1, AP-10, AP-11) wherever they say the urgency treatment is draft-only.
Scope is this change only.
Widens every urgency gate to draft-or-withdrawn (widget, table, mobile card, detail page, at-risk float, closing-soon count) and renames buildAtRiskDraftWhere/getClosingSoonDraftCount to drop the now-stale "draft" qualifier. Also retires the destructive past-deadline badge — past always renders the plain muted date regardless of status, since #748 removes Continue from past-deadline drafts. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Revision — Cycle 3withdrawn treated like draft for urgency (widget/table/mobile card/detail page/float/count), buildAtRiskDraftWhere→buildAtRiskWhere, getClosingSoonDraftCount→getClosingSoonCount; past tier now always renders muted (no destructive badge) regardless of status (issue #706 addendum 3 §8) · 3b92eb3 |
cielbellerose
left a comment
There was a problem hiding this comment.
Code Review — Cycle 8 · approved
1 open — 1 🟡 Low (see inline)
| closesAt: new Date(NOW.getTime() + 2 * DAY), | ||
| deletedAt: new Date(NOW.getTime() - 1 * HOUR), | ||
| }); | ||
| const posWithdrawnUrgent = await createTestPosition(admin, { |
There was a problem hiding this comment.
R8-L1 🟡 Low — withdrawn coverage is added only at the urgent and past-due tiers (withdrawnUrgent, withdrawnPastDue); there's no withdrawn fixture at the soon tier analogous to draftSoonOld. buildAtRiskWhere makes draft/withdrawn behave identically so this isn't a correctness risk, but addendum 3 asks for coverage "at each tier." Fix: add a posWithdrawnSoon/withdrawnSoon fixture and include it in the float/count assertions.
cielbellerose
left a comment
There was a problem hiding this comment.
Human Direction — Cycle 4
Operator-authored, not a review-agent pass. A styling rework of the deadline column, recorded in the #706 body under Scope addendum 4. Re-read that issue body before starting — addendum 4 supersedes the tier→treatment table in the original UX states section and the contrast table under it.
Why
emphasizeUrgency is consulted only in the soon and urgent branches of DeadlineIndicator, so distant returns mutedLine unconditionally. A draft closing in 10 days renders identically to an accepted application closing in 10 days — the actionable/not-actionable distinction vanishes outside the 7-day window, which defeats the feature.
The changes
1. lib/constants.ts — DEADLINE_URGENT_HOURS: 48 → 24. Leave DEADLINE_SOON_DAYS = 7 alone; it still drives the at-risk float and the closing-soon count.
2. components/features/deadline-indicator.tsx — every future deadline on an editable row is red. With emphasizeUrgency true, distant, soon and urgent all render through emphasizedLine in the destructive tone (text-destructive-text, STATE_ICONS.warning, font-semibold). They differ only in content:
urgent(≤24h) →{compactCountdown} left, e.g.9h leftsoonanddistant→ the date, via<LocalTime date={info.date} precision="date" />
With emphasizeUrgency false, all three stay mutedLine exactly as today.
upcoming and past are unchanged — both stay muted for every status. upcoming is an opening date, not a deadline, so the red rule does not reach it; past keeps the addendum 3 §8 always-muted treatment and its opacity-70.
3. Remove the amber tone. No tier renders text-warning-text any more. emphasizedLine's tone parameter is now single-valued — collapse it rather than leaving a one-branch conditional.
4. Delete the now-dead token. --warning-text does not exist on origin/dev; this PR introduced it and DeadlineIndicator is its only consumer. Remove --color-warning-text (around app/globals.css:48) and both --warning-text declarations (light ~line 97, dark ~line 144). Keep --destructive-text — still used. Verify with a grep that nothing else references warning-text before deleting.
5. compactCountdown for soon. It is no longer rendered, so getDeadlineInfo should return null for it on the soon tier, matching distant. Drop any unit assertion that expected Nd.
Consequences to encode, not to flag as bugs
soonanddistantnow render identically. Intentional. The only rendering boundary left is 24 hours.soonis retained purely becausebuildAtRiskWhereandgetClosingSoonCountstill use the 7-day window for ordering and the subtitle count — do not collapse the tier itself.- A draft three months out is the same red as one two hours out. Also intentional; red means "this is yours and the clock is running", not "emergency". This was chosen knowingly over a graduated scale.
Also close R8-L1
The cycle 8 review left one Low open: tests/db/my-applications-deadline-ordering.test.ts covers withdrawn at urgent and past-due but not soon. Add the missing soon withdrawn fixture while you are in that file.
Tests and docs
Update tests/unit/utils.test.ts for the 24h boundary (24h, 23h59m, 25h) and tests/unit/deadline-indicator.test.ts so it asserts distant and soon render red-with-icon when emphasizeUrgency is true and muted when false. Update docs/WORKFLOWS.md (AP-1, AP-10, AP-11) wherever it describes the amber tier, the 48-hour boundary, or a Nd left countdown.
Scope is this rework only.
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