Skip to content

feat(jef-57): flag likely ghosted applications - #294

Merged
mankatcheung merged 1 commit into
mainfrom
feature/jef-57-smart-likely-ghosted-flagging-for-stale-applications
Aug 8, 2026
Merged

mankatcheung merged 1 commit into
mainfrom
feature/jef-57-smart-likely-ghosted-flagging-for-stale-applications

Conversation

@mankatcheung

@mankatcheung mankatcheung commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Summary

Add computed stale-application detection without introducing a new application status.

Rule

An application is marked likely ghosted when:

  • Its status is applied or interviewing
  • It has an appliedAt timestamp
  • It has not been updated for 14 days
  • It has not received a reminder in the last 14 days

Changes

  • Add likelyGhosted to application GraphQL output
  • Apply the likelyGhosted filter server-side before pagination
  • Add URL-backed Likely ghosted filter to the applications list
  • Add amber Likely ghosted badges to list and board cards
  • Add focused classifier tests

Verification

  • API: 186 test files, 1128 tests passed
  • Web: 29 test files, 245 tests passed
  • API/web typechecks passed
  • API/web production builds passed

Summary by CodeRabbit

  • New Features
    • Added a “Likely ghosted” filter for applications.
    • Applications with no recent activity can now be identified after 14 days.
    • Added “Likely ghosted” badges to relevant application cards.
    • Updated empty states and URL filters to reflect the selected view.
  • Tests
    • Added coverage for identifying stale applications and excluding recent or completed applications.

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The API now classifies applications as likely ghosted after 14 days of inactivity. GraphQL supports filtering and exposes the classification. The applications board adds route filtering, query propagation, empty-state text, and status badges.

Changes

Likely ghosted applications

Layer / File(s) Summary
Staleness classification and API filtering
apps/api/src/use-cases/jobs/applicationStaleness.ts, apps/api/src/__tests__/application/jobs/applicationStaleness.test.ts, apps/api/src/interface-adapters/mappers/ApplicationMapper.ts, apps/api/src/http/schema/types/ApplicationType.ts, apps/api/src/use-cases/..., apps/api/src/infrastructure/db/repositories/DrizzleApplicationRepository.ts
The API classifies old active applications with no recent update or reminder. GraphQL exposes likelyGhosted, and the repository applies the corresponding filter. Tests cover active, terminal, recently edited, and recently reminded applications.
Applications route query flow
apps/web/src/routes/_authenticated/applications/index.tsx, apps/web/src/routes/_authenticated/applications/-board-queries.ts
The route validates likelyGhosted, sends it in GraphQL variables, includes it in query cache keys, and requests the returned field.
Board filtering and status display
apps/web/src/routes/_authenticated/applications/-components/ApplicationsPage.tsx, apps/web/src/routes/_authenticated/applications/-board-page.tsx
The board adds the filter control, updates the empty-state text, and displays a “Likely ghosted” badge for marked applications.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  actor Applicant
  participant ApplicationsPage
  participant applicationsPage
  participant GetApplicationsPageUseCase
  participant DrizzleApplicationRepository
  participant ApplicationMapper
  Applicant->>ApplicationsPage: select “Likely ghosted”
  ApplicationsPage->>applicationsPage: send likelyGhosted variable
  applicationsPage->>GetApplicationsPageUseCase: request filtered page
  GetApplicationsPageUseCase->>DrizzleApplicationRepository: query stale active applications
  DrizzleApplicationRepository-->>GetApplicationsPageUseCase: return matching applications
  GetApplicationsPageUseCase->>ApplicationMapper: map application results
  ApplicationMapper-->>ApplicationsPage: return likelyGhosted fields
  ApplicationsPage-->>Applicant: show filtered cards and badges
Loading

Possibly related issues

  • mankatcheung/job-finder issue 166 — The change implements the likely-ghosted classifier, filtering, and board badge described by the issue.

Possibly related PRs

Poem

A rabbit marks the cards in gold,
For quiet leads that grew too old.
Fourteen days beneath the moon,
No update, reminder, or reply soon.
A filter hops into the flow,
So ghosted applications show.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: flagging likely ghosted applications.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/jef-57-smart-likely-ghosted-flagging-for-stale-applications

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/web/src/routes/_authenticated/applications/-components/ApplicationsPage.tsx`:
- Around line 154-160: Update the likelyGhosted Link’s search configuration in
ApplicationsPage so it uses the functional search updater, preserves all
existing query parameters such as status and starred, and only toggles the
likelyGhosted value based on the current state.

In `@apps/web/src/routes/_authenticated/applications/index.tsx`:
- Around line 33-41: Update APPLICATIONS_PAGE_QUERY’s applicationsPage items
selection to include the likelyGhosted field, matching the required
$likelyGhosted variable and ensuring each application result exposes its likely
ghosted state to list cards.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 78a14a87-5404-4eaf-88b7-785fe291d234

📥 Commits

Reviewing files that changed from the base of the PR and between 72fa51f and 00bc69b.

📒 Files selected for processing (14)
  • apps/api/src/__tests__/application/jobs/applicationStaleness.test.ts
  • apps/api/src/http/schema/queries/applicationQueries.ts
  • apps/api/src/http/schema/types/ApplicationType.ts
  • apps/api/src/infrastructure/db/repositories/DrizzleApplicationRepository.ts
  • apps/api/src/interface-adapters/mappers/ApplicationMapper.ts
  • apps/api/src/interface-adapters/resolvers/ApplicationResolver.ts
  • apps/api/src/use-cases/jobs/GetApplicationsPageUseCase.ts
  • apps/api/src/use-cases/jobs/IGetApplicationsPageUseCase.ts
  • apps/api/src/use-cases/jobs/applicationStaleness.ts
  • apps/api/src/use-cases/ports/IApplicationRepository.ts
  • apps/web/src/routes/_authenticated/applications/-board-page.tsx
  • apps/web/src/routes/_authenticated/applications/-board-queries.ts
  • apps/web/src/routes/_authenticated/applications/-components/ApplicationsPage.tsx
  • apps/web/src/routes/_authenticated/applications/index.tsx

Comment on lines +154 to +160
<Link
to="/applications"
search={likelyGhosted ? {} : { likelyGhosted: true }}
className={`text-xs px-3 py-1.5 rounded-full border transition-colors ${likelyGhosted ? 'bg-amber-500 text-white border-amber-500' : 'bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-400 border-gray-200 dark:border-gray-700 hover:border-amber-400'}`}
>
Likely ghosted
</Link>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== dependency versions =="
if [ -f package.json ]; then jq -r '.dependencies["`@tanstack/react-router`"], .devDependencies["`@tanstack/react-router`"]' package.json; fi
if [ -f pnpm-lock.yaml ]; then rg -n "`@tanstack/react-router`:(.*)" pnpm-lock.yaml | head -20 || true; fi
if [ -f yarn.lock ]; then rg -n "`@tanstack/react-router`@|`@tanstack/react-router`:" yarn.lock | head -20 || true; fi
if [ -f package-lock.json ]; then jq -r '.packages["node_modules/@tanstack/react-router"].version // empty, .dependencies["`@tanstack/react-router`"].version // empty' package-lock.json 2>/dev/null || true; fi

echo
echo "== target file outline/usages =="
fd -a 'ApplicationsPage\.tsx$' . | sed 's#^\./##'
file="$(fd 'ApplicationsPage\.tsx$' apps/web/src/routes | head -1 || true)"
if [ -n "${file:-}" ]; then
  wc -l "$file"
  ast-grep outline "$file" --view compact || true
  sed -n '1,220p' "$file"
fi

echo
echo "== likelyGhosted status starred search usages =="
rg -n "likelyGhosted|status|starred" apps/web/src/routes -g '*.tsx' -g '*.ts' || true

Repository: mankatcheung/job-finder

Length of output: 26720


🌐 Web query:

TanStack Router navigate search functional updater preserves search params search prop

💡 Result:

In TanStack Router, using a functional updater with the search property is the recommended way to modify search parameters while preserving existing ones [1][2]. When you provide a function to the search prop (in the Link component) or the search option (in navigate or useNavigate), the router passes the current search parameters as an argument to that function [1][3]. You can then return a new object containing your updates, merging it with the previous parameters [1][2]. Example usage: // Using navigate navigate({ search: (prev) => ({...prev, page: 1 }), }) // Using Link <Link search={(prev) => ({...prev, page: 1 })}> Next Page By using the spread operator ({...prev,... }), you ensure that any existing search parameters—including those inherited from parent routes—are maintained [2]. If you instead provide a plain object as the search value, it will typically replace or overwrite the existing search parameters [2]. For more complex scenarios, such as needing to remove specific parameters, you can destructure them within the functional updater: navigate({ search: (prev) => { const { unwantedParam,...rest } = prev; return rest; }, }) This functional approach is essential for maintaining application state that is stored in the URL, especially in deeply nested route structures where parent search parameters must be preserved during navigation [2].

Citations:


Preserve existing URL filters when toggling likelyGhosted.

This search prop replaces the current search state. If status or starred is active, clicking the link removes those filters when it adds or removes likelyGhosted. Use the functional search updater and keep the previous parameters.

Proposed fix
-          search={likelyGhosted ? {} : { likelyGhosted: true }}
+          search={(prev) => {
+            if (likelyGhosted) {
+              const next = { ...prev };
+              delete next.likelyGhosted;
+              return next;
+            }
+            return { ...prev, likelyGhosted: true };
+          }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Link
to="/applications"
search={likelyGhosted ? {} : { likelyGhosted: true }}
className={`text-xs px-3 py-1.5 rounded-full border transition-colors ${likelyGhosted ? 'bg-amber-500 text-white border-amber-500' : 'bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-400 border-gray-200 dark:border-gray-700 hover:border-amber-400'}`}
>
Likely ghosted
</Link>
<Link
to="/applications"
search={(prev) => {
if (likelyGhosted) {
const next = { ...prev };
delete next.likelyGhosted;
return next;
}
return { ...prev, likelyGhosted: true };
}}
className={`text-xs px-3 py-1.5 rounded-full border transition-colors ${likelyGhosted ? 'bg-amber-500 text-white border-amber-500' : 'bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-400 border-gray-200 dark:border-gray-700 hover:border-amber-400'}`}
>
Likely ghosted
</Link>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/web/src/routes/_authenticated/applications/-components/ApplicationsPage.tsx`
around lines 154 - 160, Update the likelyGhosted Link’s search configuration in
ApplicationsPage so it uses the functional search updater, preserves all
existing query parameters such as status and starred, and only toggles the
likelyGhosted value based on the current state.

Comment on lines +33 to +41
$likelyGhosted: Boolean
) {
applicationsPage(
status: $status
starred: $starred
search: $search
cursor: $cursor
limit: $limit
likelyGhosted: $likelyGhosted

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Select likelyGhosted in APPLICATIONS_PAGE_QUERY.

Line 76 declares likelyGhosted as required. The items selection does not request this field. GraphQL therefore omits it from list results, and list cards cannot display the likely ghosted state.

Proposed fix
       items {
         id
         company
         role
         status
+        likelyGhosted
         location

Also applies to: 76-76

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/routes/_authenticated/applications/index.tsx` around lines 33 -
41, Update APPLICATIONS_PAGE_QUERY’s applicationsPage items selection to include
the likelyGhosted field, matching the required $likelyGhosted variable and
ensuring each application result exposes its likely ghosted state to list cards.

@mankatcheung
mankatcheung merged commit 4b35bfe into main Aug 8, 2026
11 checks passed
@mankatcheung
mankatcheung deleted the feature/jef-57-smart-likely-ghosted-flagging-for-stale-applications branch August 18, 2026 13:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant