Skip to content

fix: replace explicit any usage with real types - #372

Merged
mankatcheung merged 3 commits into
mainfrom
worktree-fix-explicit-any-usages
Aug 11, 2026
Merged

mankatcheung merged 3 commits into
mainfrom
worktree-fix-explicit-any-usages

Conversation

@mankatcheung

@mankatcheung mankatcheung commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes all 17 hand-written any occurrences in the codebase (apps/api + apps/web; packages/ui was already clean). Found via pnpm lint's @typescript-eslint/no-explicit-any warnings (14) plus a direct grep cross-check that surfaced 3 more suppressed via inline eslint-disable comments. apps/web/src/routeTree.gen.ts (TanStack Router's generated file, already blanket eslint-disable + @ts-nocheck) is untouched — not hand-written.

  • schema.ts: the Drizzle forward-referenced FK callback (document.sourceDraftIddocumentDraft.id, defined later in the file) now returns AnySQLiteColumn — Drizzle's own documented type for exactly this circular/forward-reference pattern — instead of any.
  • ReactPdfDocumentRenderer.tsx: textStyle now typed as Style from @react-pdf/types, added as a proper devDependency instead of relying on it as an undeclared transitive dependency of @react-pdf/renderer (pnpm's strict linking correctly blocked the phantom import).
  • setup.ts: turns out no cast was needed at all — globalThis.localStorage is already ambiently typed as Storage in TS's DOM lib; the mock object structurally satisfies it.
  • 6 optimistic-update setQueryData/getQueryData call sites (ApplicationDetailPage, ContactsTab, DocumentsTab x2, InterviewsTab, ApplicationsPage) — each of these files already has the same mutation pattern typed correctly elsewhere; these one-off instances were just missing the same generic. ApplicationsPage's was the trickiest (an infinite-query cache value) — typed as InfiniteData<ApplicationsPageResult> using the app's own already-exported result type.
  • useDebouncedCallback.ts's generic constraint any[] is kept — a documented, legitimate TypeScript pattern for generic callback-wrapping utilities (unknown[] breaks bivariant inference for callbacks with specific parameter types). Replaced the per-line disable comment with @typescript-eslint/no-explicit-any's own ignoreRestArgs option in eslint.config.mjs, so the exception is a real, permanent config decision instead of a silent one-off suppression.

Test plan

  • pnpm --filter @job-finder/api typecheck / test (209 files, 1275 tests) — pass
  • pnpm --filter @job-finder/web typecheck / test (60 files, 407 tests) — pass
  • pnpm lint (full workspace) — 0 errors, 0 warnings (previously 14 no-explicit-any warnings)
  • pnpm build — all 5 workspace packages build successfully
  • Grepped the whole repo post-fix for any remaining any-type patterns — only the intentional useDebouncedCallback.ts one remains, now covered by the config option instead of a suppression comment

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Bulk deletion now immediately removes selected applications from the correct filtered list while the request completes.
    • Undo and restoration continue to work reliably across application-related records.
  • Refactor

    • Improved type safety across document rendering, database schema handling, and application data operations.
    • Reduced reliance on untyped values while preserving existing deletion, undo, and rendering behaviour.
  • Chores

    • Updated development tooling and linting configuration to support improved type definitions.

Fixes all 17 hand-written any occurrences found across the codebase
(apps/api and apps/web; packages/ui was already clean):

- schema.ts: the Drizzle forward-referenced FK callback now returns
  AnySQLiteColumn (Drizzle's own documented type for exactly this
  circular/forward-reference pattern) instead of any.
- ReactPdfDocumentRenderer.tsx: textStyle now typed as Style from
  @react-pdf/types (added as a proper devDependency instead of relying
  on it as an undeclared transitive dependency of @react-pdf/renderer).
- setup.ts: the localStorage polyfill needed no cast at all —
  globalThis.localStorage is already ambiently typed as Storage.
- The 6 optimistic-update setQueryData/getQueryData callsites
  (ApplicationDetailPage, ContactsTab, DocumentsTab x2, InterviewsTab,
  ApplicationsPage) now pass the same typed generic every other
  setQueryData call in each of those files already used, instead of
  leaving this one instance untyped.
- useDebouncedCallback.ts's generic constraint any[] is kept — a
  documented, legitimate TypeScript pattern for generic
  callback-wrapping utilities (unknown[] breaks bivariant inference
  for specific-parameter callbacks) — but the per-line disable comment
  is replaced with @typescript-eslint/no-explicit-any's own
  ignoreRestArgs option in eslint.config.mjs, so the exception is a
  real config decision instead of a silent one-off suppression.

routeTree.gen.ts (TanStack Router's generated file, already
eslint-disabled/@ts-nocheck) is untouched — not hand-written code.
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@mankatcheung, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 49 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b977239f-0103-4651-9d52-db4930feb592

📥 Commits

Reviewing files that changed from the base of the PR and between 68166f9 and c493882.

📒 Files selected for processing (1)
  • apps/web/src/__tests__/components/ApplicationsPage.test.tsx

Walkthrough

The pull request replaces selected any usage with explicit types across API and web code. It also centralises application query-key construction and types optimistic deletion updates for filtered React Query caches.

Changes

Type safety and optimistic cache updates

Layer / File(s) Summary
API type annotations
apps/api/package.json, apps/api/src/infrastructure/db/schema.ts, apps/api/src/infrastructure/pdf/ReactPdfDocumentRenderer.tsx
The API uses AnySQLiteColumn for the document self-reference and Style for PDF text styles. The React PDF types package is added.
Web lint and test typing
apps/web/eslint.config.mjs, apps/web/src/__tests__/setup.ts, apps/web/src/hooks/useDebouncedCallback.ts
The lint rule ignores rest arguments. The localStorage mock no longer uses an any cast. An obsolete suppression is removed.
Optimistic cache typing
apps/web/src/routes/_authenticated/applications/..., apps/web/src/__tests__/components/ApplicationsPage.test.tsx
Deletion handlers use typed React Query snapshots and updater values. Application bulk deletion targets the exact filtered query key and includes an optimistic-update test.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Poem

A rabbit types the cache with care,
And keeps stray any out of there.
Filtered keys now guide the way,
While deleted rows hop away.
PDF styles rest neat and fair.

🚥 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 summarises the primary change: replacing explicit any usage with specific types.
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 worktree-fix-explicit-any-usages

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 306-319: Update the onDelete optimistic deletion flow to use the
complete query key produced by applicationsPageQueryOptions, including status,
starred, likelyGhosted, and searchTerm, for its getQueryData, setQueryData, and
rollback/restore operations. Ensure deletion and undo target the currently
visible filtered applications query rather than only ['applications', 'page'].

In
`@apps/web/src/routes/_authenticated/applications/`$applicationId/-components/ApplicationDetailPage.tsx:
- Around line 484-493: Add tests covering optimistic deletion and undo
restoration for all six handlers, using a controllable showUndoToast mock:
ApplicationDetailPage.tsx lines 484-493, ContactsTab.tsx lines 350-359,
DocumentsTab.tsx lines 242-251 and 396-405, InterviewsTab.tsx lines 425-436, and
ApplicationsPage.tsx lines 306-319. Verify document-draft deletion, optimistic
cache removal, restoration on undo, and removal of the deleted item across every
ApplicationsPage pagination page; retain existing mutation-request assertions.
🪄 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: 3e4f3d27-cf0a-499f-a943-a8409f43a2ee

📥 Commits

Reviewing files that changed from the base of the PR and between 63edb65 and 74a749d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (11)
  • apps/api/package.json
  • apps/api/src/infrastructure/db/schema.ts
  • apps/api/src/infrastructure/pdf/ReactPdfDocumentRenderer.tsx
  • apps/web/eslint.config.mjs
  • apps/web/src/__tests__/setup.ts
  • apps/web/src/hooks/useDebouncedCallback.ts
  • apps/web/src/routes/_authenticated/applications/$applicationId/-components/ApplicationDetailPage.tsx
  • apps/web/src/routes/_authenticated/applications/$applicationId/-components/ContactsTab.tsx
  • apps/web/src/routes/_authenticated/applications/$applicationId/-components/DocumentsTab.tsx
  • apps/web/src/routes/_authenticated/applications/$applicationId/-components/InterviewsTab.tsx
  • apps/web/src/routes/_authenticated/applications/-components/ApplicationsPage.tsx
💤 Files with no reviewable changes (1)
  • apps/web/src/hooks/useDebouncedCallback.ts

Comment thread apps/web/src/routes/_authenticated/applications/-components/ApplicationsPage.tsx Outdated
Comment on lines +484 to +493
const snapshot = qc.getQueryData<{ notes: Note[] }>([
'notes',
applicationId,
]);
qc.setQueryData<{ notes: Note[] }>(
['notes', applicationId],
(prev) => ({
notes: (prev?.notes ?? []).filter((n) => n.id !== note.id),
}),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '=== diff stat ==='
git diff --stat

printf '%s\n' '=== changed files ==='
git diff --name-only

printf '%s\n' '=== candidate test files ==='
git ls-files | rg '(^|/)(__tests__/|.*\.(test|spec)\.(ts|tsx)$)' | rg 'ApplicationDetailPage|ContactsTab|DocumentsTab|InterviewsTab|ApplicationsPage|applications'

printf '%s\n' '=== relevant symbols and test references ==='
rg -n --glob '*.ts' --glob '*.tsx' \
  'ApplicationDetailPage|ContactsTab|DocumentsTab|InterviewsTab|ApplicationsPage|optimistic|undo|restore|setQueryData|getQueryData' \
  apps/web/src/routes/_authenticated/applications

Repository: mankatcheung/job-finder

Length of output: 15552


🏁 Script executed:

#!/bin/bash
set -eu

for f in \
  apps/web/src/__tests__/components/ApplicationDetailPage.test.tsx \
  apps/web/src/__tests__/components/ApplicationsPage.test.tsx \
  apps/web/src/__tests__/components/ContactsTab.test.tsx \
  apps/web/src/__tests__/components/DocumentsTab.test.tsx \
  apps/web/src/__tests__/components/InterviewsTab.test.tsx
do
  printf '\n=== %s ===\n' "$f"
  wc -l "$f"
  ast-grep outline "$f" || true
  printf '%s\n' '--- targeted references ---'
  rg -n -C 5 \
    'delete|remove|undo|showUndoToast|onUndo|notes|contacts|documentDrafts|documents|interviewRounds|applications|infinite|page' \
    "$f" || true
done

Repository: mankatcheung/job-finder

Length of output: 36398


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '=== ApplicationDetailPage handler and test ==='
sed -n '470,505p' \
  apps/web/src/routes/_authenticated/applications/\$applicationId/-components/ApplicationDetailPage.tsx
sed -n '475,510p' \
  apps/web/src/__tests__/components/ApplicationDetailPage.test.tsx

printf '%s\n' '=== ContactsTab handler and test ==='
sed -n '340,370p' \
  apps/web/src/routes/_authenticated/applications/\$applicationId/-components/ContactsTab.tsx
sed -n '120,151p' \
  apps/web/src/__tests__/components/ContactsTab.test.tsx

printf '%s\n' '=== DocumentsTab handlers and test ==='
sed -n '232,265p;386,418p' \
  apps/web/src/routes/_authenticated/applications/\$applicationId/-components/DocumentsTab.tsx
sed -n '108,138p' \
  apps/web/src/__tests__/components/DocumentsTab.test.tsx

printf '%s\n' '=== InterviewsTab handler and test ==='
sed -n '415,450p' \
  apps/web/src/routes/_authenticated/applications/\$applicationId/-components/InterviewsTab.tsx
sed -n '100,128p' \
  apps/web/src/__tests__/components/InterviewsTab.test.tsx

printf '%s\n' '=== ApplicationsPage handler and tests ==='
sed -n '295,340p' \
  apps/web/src/routes/_authenticated/applications/-components/ApplicationsPage.tsx
sed -n '430,485p' \
  apps/web/src/__tests__/components/ApplicationsPage.test.tsx

Repository: mankatcheung/job-finder

Length of output: 16323


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from pathlib import Path
import re

sites = [
    (
        "ApplicationDetailPage",
        Path("apps/web/src/routes/_authenticated/applications/$applicationId/-components/ApplicationDetailPage.tsx"),
        Path("apps/web/src/__tests__/components/ApplicationDetailPage.test.tsx"),
        "notes",
        "deletes a note",
    ),
    (
        "ContactsTab",
        Path("apps/web/src/routes/_authenticated/applications/$applicationId/-components/ContactsTab.tsx"),
        Path("apps/web/src/__tests__/components/ContactsTab.test.tsx"),
        "contacts",
        "deletes a contact",
    ),
    (
        "DocumentsTab draft",
        Path("apps/web/src/routes/_authenticated/applications/$applicationId/-components/DocumentsTab.tsx"),
        Path("apps/web/src/__tests__/components/DocumentsTab.test.tsx"),
        "documentDrafts",
        "deletes a draft",
    ),
    (
        "DocumentsTab document",
        Path("apps/web/src/routes/_authenticated/applications/$applicationId/-components/DocumentsTab.tsx"),
        Path("apps/web/src/__tests__/components/DocumentsTab.test.tsx"),
        "documents",
        "deletes a document",
    ),
    (
        "InterviewsTab",
        Path("apps/web/src/routes/_authenticated/applications/$applicationId/-components/InterviewsTab.tsx"),
        Path("apps/web/src/__tests__/components/InterviewsTab.test.tsx"),
        "interviewRounds",
        "deletes an interview round",
    ),
    (
        "ApplicationsPage",
        Path("apps/web/src/routes/_authenticated/applications/-components/ApplicationsPage.tsx"),
        Path("apps/web/src/__tests__/components/ApplicationsPage.test.tsx"),
        "applications",
        "bulk-deletes selected applications",
    ),
]

for name, source_path, test_path, cache_key, test_name in sites:
    source = source_path.read_text()
    tests = test_path.read_text()
    implementation_has_undo = (
        f"setQueryData(['{cache_key}'" in source
        and "onUndo:" in source
    )
    match = re.search(
        rf"\bit\(['\"]{re.escape(test_name)}['\"].*?(?=\n\s*\}\);|\n\s*\}}\);)",
        tests,
        re.S,
    )
    block = match.group(0) if match else ""
    has_cache_assertion = any(
        token in block
        for token in ("getQueryData", "setQueryData", "getQueryData", "getQueryCache", "queryClient.get")
    )
    has_undo_assertion = "onUndo" in block or "undo" in block.lower() and "expect" in block
    print(
        f"{name}: implementation_onUndo={implementation_has_undo}; "
        f"delete_test={'present' if match else 'missing'}; "
        f"cache_assertion={has_cache_assertion}; undo_assertion={has_undo_assertion}"
    )

mock = Path("apps/web/src/__tests__/components/ApplicationDetailPage.test.tsx").read_text()
print(
    "shared_mock: "
    f"executes_onExecute={'onExecute();' in mock}; "
    f"invokes_onUndo={'onUndo' in mock.split('vi.mock(\\'`#/lib/undoToast`\\'')[1].split('}));', 1)[0]}"
)
PY

Repository: mankatcheung/job-finder

Length of output: 390


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from pathlib import Path
import re

sites = [
    (
        "ApplicationDetailPage",
        Path("apps/web/src/routes/_authenticated/applications/$applicationId/-components/ApplicationDetailPage.tsx"),
        Path("apps/web/src/__tests__/components/ApplicationDetailPage.test.tsx"),
        "notes",
        "deletes a note",
    ),
    (
        "ContactsTab",
        Path("apps/web/src/routes/_authenticated/applications/$applicationId/-components/ContactsTab.tsx"),
        Path("apps/web/src/__tests__/components/ContactsTab.test.tsx"),
        "contacts",
        "deletes a contact",
    ),
    (
        "DocumentsTab draft",
        Path("apps/web/src/routes/_authenticated/applications/$applicationId/-components/DocumentsTab.tsx"),
        Path("apps/web/src/__tests__/components/DocumentsTab.test.tsx"),
        "documentDrafts",
        "deletes a draft",
    ),
    (
        "DocumentsTab document",
        Path("apps/web/src/routes/_authenticated/applications/$applicationId/-components/DocumentsTab.tsx"),
        Path("apps/web/src/__tests__/components/DocumentsTab.test.tsx"),
        "documents",
        "deletes a document",
    ),
    (
        "InterviewsTab",
        Path("apps/web/src/routes/_authenticated/applications/$applicationId/-components/InterviewsTab.tsx"),
        Path("apps/web/src/__tests__/components/InterviewsTab.test.tsx"),
        "interviewRounds",
        "deletes an interview round",
    ),
    (
        "ApplicationsPage",
        Path("apps/web/src/routes/_authenticated/applications/-components/ApplicationsPage.tsx"),
        Path("apps/web/src/__tests__/components/ApplicationsPage.test.tsx"),
        "applications",
        "bulk-deletes selected applications",
    ),
]

for name, source_path, test_path, cache_key, test_name in sites:
    source = source_path.read_text()
    tests = test_path.read_text()
    implementation_has_undo = (
        f"setQueryData(['{cache_key}'" in source
        and "onUndo:" in source
    )
    match = re.search(
        rf"\bit\(['\"]{re.escape(test_name)}['\"].*?(?=\n\s*\}\);|\n\s*\}}\);)",
        tests,
        re.S,
    )
    block = match.group(0) if match else ""
    has_cache_assertion = any(
        token in block
        for token in ("getQueryData", "setQueryData", "getQueryCache", "queryClient.get")
    )
    has_undo_assertion = "onUndo" in block or (
        "undo" in block.lower() and "expect" in block
    )
    print(
        f"{name}: implementation_onUndo={implementation_has_undo}; "
        f"delete_test={'present' if match else 'missing'}; "
        f"cache_assertion={has_cache_assertion}; undo_assertion={has_undo_assertion}"
    )

mock = Path("apps/web/src/__tests__/components/ApplicationDetailPage.test.tsx").read_text()
mock_section = mock.split("vi.mock('`#/lib/undoToast`'", 1)[1].split("}));", 1)[0]
print(
    "shared_mock: "
    f"executes_onExecute={'onExecute();' in mock_section}; "
    f"invokes_onUndo={'onUndo' in mock_section}"
)
PY

Repository: mankatcheung/job-finder

Length of output: 390


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from pathlib import Path
import re

sites = [
    (
        "ApplicationDetailPage",
        "apps/web/src/routes/_authenticated/applications/$applicationId/-components/ApplicationDetailPage.tsx",
        "apps/web/src/__tests__/components/ApplicationDetailPage.test.tsx",
        "deletes a note",
    ),
    (
        "ContactsTab",
        "apps/web/src/routes/_authenticated/applications/$applicationId/-components/ContactsTab.tsx",
        "apps/web/src/__tests__/components/ContactsTab.test.tsx",
        "deletes a contact",
    ),
    (
        "DocumentsTab draft",
        "apps/web/src/routes/_authenticated/applications/$applicationId/-components/DocumentsTab.tsx",
        "apps/web/src/__tests__/components/DocumentsTab.test.tsx",
        "deletes a draft",
    ),
    (
        "DocumentsTab document",
        "apps/web/src/routes/_authenticated/applications/$applicationId/-components/DocumentsTab.tsx",
        "apps/web/src/__tests__/components/DocumentsTab.test.tsx",
        "deletes a document",
    ),
    (
        "InterviewsTab",
        "apps/web/src/routes/_authenticated/applications/$applicationId/-components/InterviewsTab.tsx",
        "apps/web/src/__tests__/components/InterviewsTab.test.tsx",
        "deletes an interview round",
    ),
    (
        "ApplicationsPage",
        "apps/web/src/routes/_authenticated/applications/-components/ApplicationsPage.tsx",
        "apps/web/src/__tests__/components/ApplicationsPage.test.tsx",
        "bulk-deletes selected applications",
    ),
]

for name, source_name, test_name_path, test_name in sites:
    source = Path(source_name).read_text()
    tests = Path(test_name_path).read_text()
    pattern = (
        r"\bit\(['\"]" + re.escape(test_name)
        + r"['\"].*?(?=\n\s*\}\);)"
    )
    match = re.search(pattern, tests, re.S)
    block = match.group(0) if match else ""
    print(
        f"{name}: "
        f"implementation_onUndo={'onUndo:' in source}; "
        f"delete_test={'present' if match else 'missing'}; "
        f"cache_assertion={any(x in block for x in ('getQueryData', 'setQueryData', 'getQueryCache'))}; "
        f"undo_assertion={'onUndo' in block or ('undo' in block.lower() and 'expect' in block)}"
    )

mock = Path("apps/web/src/__tests__/components/ApplicationDetailPage.test.tsx").read_text()
mock_section = mock.split("vi.mock('`#/lib/undoToast`'", 1)[1].split("}));", 1)[0]
print(
    "shared_mock: "
    f"executes_onExecute={'onExecute();' in mock_section}; "
    f"invokes_onUndo={'onUndo' in mock_section}"
)
PY

Repository: mankatcheung/job-finder

Length of output: 890


Add coverage for optimistic deletion and undo restoration.

The existing deletion tests assert mutation requests only. No test covers document-draft deletion, optimistic cache removal, or undo restoration. Use a controllable showUndoToast mock and cover all six handlers, including removal across all ApplicationsPage pages.

📍 Affects 5 files
  • apps/web/src/routes/_authenticated/applications/$applicationId/-components/ApplicationDetailPage.tsx#L484-L493 (this comment)
  • apps/web/src/routes/_authenticated/applications/$applicationId/-components/ContactsTab.tsx#L350-L359
  • apps/web/src/routes/_authenticated/applications/$applicationId/-components/DocumentsTab.tsx#L242-L251
  • apps/web/src/routes/_authenticated/applications/$applicationId/-components/DocumentsTab.tsx#L396-L405
  • apps/web/src/routes/_authenticated/applications/$applicationId/-components/InterviewsTab.tsx#L425-L436
  • apps/web/src/routes/_authenticated/applications/-components/ApplicationsPage.tsx#L306-L319
🤖 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/`$applicationId/-components/ApplicationDetailPage.tsx
around lines 484 - 493, Add tests covering optimistic deletion and undo
restoration for all six handlers, using a controllable showUndoToast mock:
ApplicationDetailPage.tsx lines 484-493, ContactsTab.tsx lines 350-359,
DocumentsTab.tsx lines 242-251 and 396-405, InterviewsTab.tsx lines 425-436, and
ApplicationsPage.tsx lines 306-319. Verify document-draft deletion, optimistic
cache removal, restoration on undo, and removal of the deleted item across every
ApplicationsPage pagination page; retain existing mutation-request assertions.

Source: Coding guidelines

CodeRabbit review on #372 flagged that the bulk onDelete handler's
optimistic update used an incomplete query key (['applications',
'page']) instead of the full key applicationsPageQueryOptions actually
registers the query under (status, starred, likelyGhosted, searchTerm
included). getQueryData/setQueryData require an exact key match — so
the optimistic removal and its undo-restore were silently no-ops
whenever any filter was active, leaving the deleted rows visible until
the mutation's eventual invalidation refetch caught up.

Extracts applicationsPageQueryKey() as the single source of truth for
this key (used by both applicationsPageQueryOptions and the delete
handler), and threads status/starred/likelyGhosted/searchTerm down to
BulkActionBar so it can build the matching key.

Adds a regression test verifying the optimistic removal specifically:
confirmed it fails against the pre-fix code (Stripe/Vercel stay
visible) and passes with the fix.

Not adding equivalent tests for the other 5 optimistic-update handlers
CodeRabbit's second comment named (ApplicationDetailPage, ContactsTab,
DocumentsTab x2, InterviewsTab) — verified each already uses the exact
same simple query key its own useQuery call uses, so there's no
matching bug there; that suggestion is general coverage improvement
unrelated to a defect, out of scope for this fix.
@mankatcheung

Copy link
Copy Markdown
Owner Author

Addressed CodeRabbit's review (commit 68166f9):

Finding 1 (real bug, fixed): the onDelete optimistic update in ApplicationsPage.tsx's BulkActionBar used an incomplete query key (['applications', 'page']) instead of the full key applicationsPageQueryOptions actually registers the query under (status, starred, likelyGhosted, searchTerm included). Since getQueryData/setQueryData need an exact key match, the optimistic removal — and its undo-restore — silently did nothing whenever any filter was active. Fixed by extracting applicationsPageQueryKey() as the single source of truth (used by both the query options and the delete handler) and threading the filter state down to BulkActionBar. Added a regression test that I verified fails against the pre-fix code and passes with the fix.

Finding 2 (test coverage suggestion): reviewed each of the other 5 handlers named (ApplicationDetailPage, ContactsTab, DocumentsTab x2, InterviewsTab) — each already uses the exact same simple query key its own useQuery call uses, so there's no matching bug there. Skipping additional tests for those; it's general coverage improvement unrelated to a defect, not something this fix-PR needs to carry.

@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: 1

🧹 Nitpick comments (1)
apps/web/src/__tests__/components/ApplicationsPage.test.tsx (1)

459-481: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Exercise a non-default filter in this regression test.

The test renders the default route state, so it only exercises the empty-filter cache key. It does not verify that status, starred, likelyGhosted, and searchTerm remain aligned when a filter is active. Configure at least one non-default filter before deletion and keep the assertion that rows disappear before the refetch returns.

As per coding guidelines, every new or changed utility function must ship with matching tests in the same change. This also matches the PR objective to cover filtered optimistic deletion.

🤖 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/__tests__/components/ApplicationsPage.test.tsx` around lines 459
- 481, Update the “optimistically removes deleted applications from the list”
test to render or configure a non-default filter before selecting and deleting
applications, exercising the aligned status, starred, likelyGhosted, and
searchTerm cache-key values. Preserve the assertions that deleted rows disappear
before refetch and that the delete mutation is requested; add or update
same-change tests for any new or modified utility used to configure or handle
the filtered state.

Source: Coding guidelines

🤖 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/index.tsx`:
- Around line 89-103: Update applicationsPageQueryKey so undefined starred and
likelyGhosted values map to null rather than false, preserving explicit false
values as false. Keep the query key aligned with applicationsPageQueryOptions to
ensure absent filters and false filters produce distinct keys, including for
optimistic updates in ApplicationsPage.

---

Nitpick comments:
In `@apps/web/src/__tests__/components/ApplicationsPage.test.tsx`:
- Around line 459-481: Update the “optimistically removes deleted applications
from the list” test to render or configure a non-default filter before selecting
and deleting applications, exercising the aligned status, starred,
likelyGhosted, and searchTerm cache-key values. Preserve the assertions that
deleted rows disappear before refetch and that the delete mutation is requested;
add or update same-change tests for any new or modified utility used to
configure or handle the filtered state.
🪄 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: 74bb0d96-b2b9-4d6e-a4d9-be55ea223471

📥 Commits

Reviewing files that changed from the base of the PR and between 74a749d and 68166f9.

📒 Files selected for processing (3)
  • apps/web/src/__tests__/components/ApplicationsPage.test.tsx
  • apps/web/src/routes/_authenticated/applications/-components/ApplicationsPage.tsx
  • apps/web/src/routes/_authenticated/applications/index.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/src/routes/_authenticated/applications/-components/ApplicationsPage.tsx

Comment on lines +89 to +103
export function applicationsPageQueryKey(
status: string | undefined,
starred: boolean | undefined,
searchTerm: string,
likelyGhosted: boolean | undefined,
) {
return [
'applications',
'page',
status ?? null,
starred ?? false,
likelyGhosted ?? false,
searchTerm,
] as const;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Keep an absent filter distinct from false.

Lines [98-100] map undefined to false, but applicationsPageQueryOptions sends null for an absent starred filter and preserves an explicit false. It also omits an absent likelyGhosted value but sends false when supplied. These requests can therefore share one TanStack Query key while representing different filters.

apps/web/src/routes/_authenticated/applications/-components/ApplicationsPage.tsx Line [326] uses this key for optimistic updates, so the collision can display or update the wrong filtered list. Use a canonical absence value that is distinct from false, such as null, for both key segments.

Proposed fix
-    starred ?? false,
-    likelyGhosted ?? false,
+    starred ?? null,
+    likelyGhosted ?? null,
📝 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
export function applicationsPageQueryKey(
status: string | undefined,
starred: boolean | undefined,
searchTerm: string,
likelyGhosted: boolean | undefined,
) {
return [
'applications',
'page',
status ?? null,
starred ?? false,
likelyGhosted ?? false,
searchTerm,
] as const;
}
export function applicationsPageQueryKey(
status: string | undefined,
starred: boolean | undefined,
searchTerm: string,
likelyGhosted: boolean | undefined,
) {
return [
'applications',
'page',
status ?? null,
starred ?? null,
likelyGhosted ?? null,
searchTerm,
] as const;
}
🤖 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 89 -
103, Update applicationsPageQueryKey so undefined starred and likelyGhosted
values map to null rather than false, preserving explicit false values as false.
Keep the query key aligned with applicationsPageQueryOptions to ensure absent
filters and false filters produce distinct keys, including for optimistic
updates in ApplicationsPage.

…gression test

CodeRabbit nitpick on #372: the existing regression test only rendered
the default (no-filter) route state, so it only exercised the
empty-filter cache key — not the actual scenario the original bug was
about (the optimistic delete missing the currently-visible *filtered*
query's cache entry). Adds a second test with status='applied' active,
keeping the same disappearing-rows assertion.
@mankatcheung

Copy link
Copy Markdown
Owner Author

Fixed the nitpick (commit c493882): added a second regression test with an active filter (`status: 'applied'`), so the delete-optimistic-update test covers the actual filtered-query scenario the original bug was about, not just the empty-filter default state.

@mankatcheung
mankatcheung merged commit 534cc60 into main Aug 11, 2026
12 checks passed
@mankatcheung
mankatcheung deleted the worktree-fix-explicit-any-usages branch August 12, 2026 14:34
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