Skip to content

Launch readiness fixes: search filters + JD parser + tier quota + UI polish#6

Merged
LEANDERANTONY merged 2 commits into
mainfrom
feat/launch-readiness-fixes
May 26, 2026
Merged

Launch readiness fixes: search filters + JD parser + tier quota + UI polish#6
LEANDERANTONY merged 2 commits into
mainfrom
feat/launch-readiness-fixes

Conversation

@LEANDERANTONY

@LEANDERANTONY LEANDERANTONY commented May 26, 2026

Copy link
Copy Markdown
Owner

Summary

Eight pre-launch fixes surfaced by the in-extension UI test against the workspace. Branched off latest main so the diff is just these changes (no revert noise).

Backend (2d75bae)

  • Job search filters silently no-op'd. JobSearchService.search_cached rebuilt normalized_query without copying work_modes / employment_types / sort_by, then called store.search() without those kwargs either. Chip flipped in the UI and label flipped in the dropdown but the request never reached the RPC with the new value. Threaded end-to-end through both search_cached and the live-source fallback; offset pagination from main preserved alongside.
  • Business tier daily quota fell through to FREE. get_daily_quota_for_plan had no "business" branch — paying customers got the 12 calls / 60k tokens free cap on the daily safety-net limiter. Now bucketed with Pro (monthly TIER_CAPS still does tier differentiation).
  • JD parser leaked section headers and benefits into requirements. The n8n "AI Product Builder" listing surfaced the literal string "REQUIREMENTS / MUST-HAVES" as a Must-Have item, and a "Benefits" block adjacent to "Nice to have" pulled vacation / PTO / medical / HSA / 401(k) into nice_to_haves. Tightened the prompt + added two deterministic post-scrubs (_is_section_label_artifact, _looks_like_benefit) wired through _coerce_string_list flags.

Frontend (f78bf37)

  • Capitalize plan_tier label in the account popover via a new formatTier() helper.
  • Relabel "Runs left" for unlimited tiers"Unlimited (Internal)" / "Unlimited (Admin)".
  • Inline CTA on the locked Analysis tab. aria-disabled instead of disabled; click routes to the missing prereq step (Step 01 if no résumé, Step 03 if no JD) and surfaces a warning notice.
  • Match-score tile on Job Detail before analysis runs — shows "—" with hint "Run analysis to compute" (or "Re-run analysis (inputs changed)" when stale).

Test plan

  • pytest tests/test_jd_llm_parser_service.py tests/test_job_search_service.py tests/test_quota_service.py tests/test_jd_parser.py tests/test_cached_jobs_store.py — 61 passed
  • ruff check clean on touched Python files
  • tsc --noEmit clean
  • eslint clean on touched frontend files
  • Log in as Business account, confirm popover shows "Business" (not "business")
  • Paste a JD, navigate to Job Detail, confirm Match Score tile shows "— Run analysis to compute"
  • Click locked Analysis rail step before uploading résumé — should route to Step 01 with a warning notice
  • Search jobs, toggle "Remote" filter — non-remote roles should disappear
  • Change Sort dropdown to "Most recent" — order should change

Summary by CodeRabbit

  • New Features

    • Job search now preserves filter selections for work modes, employment types, and sort order.
    • Locked step rail items now show prerequisite requirements when clicked, with navigation support.
    • JD analysis status displays helpful hints when results are stale or not yet computed.
    • Plan tier names now display in normalized format (Free/Pro/Business/Admin).
  • Improvements

    • Enhanced JD parsing to filter out compensation-related items from requirements.
    • Improved locked step visual styling and accessibility.

Review Change Stack

LEANDERANTONY and others added 2 commits May 27, 2026 02:12
Three independent backend fixes the UI test surfaced, batched into one
commit because they each carry a focused regression test.

  * Job search filters silently no-op'd. JobSearchService.search_cached
    rebuilt normalized_query without copying work_modes /
    employment_types / sort_by, then called store.search() without
    those kwargs either. Result: the chip flipped in the UI and the
    label flipped in the dropdown but the request never reached the
    RPC with the new value (Remote filter + Newest sort, reported in
    the in-extension UI test). Now threaded end-to-end through both
    search_cached and the live-source fallback; new regression test
    pins the contract.

  * get_daily_quota_for_plan had no "business" branch. Business users
    fell through to the FREE caps (12 calls / 60k tokens per day) on
    the daily cost-limiter, silently throttling paying customers
    mid-workflow. The monthly TIER_CAPS table grants Business generous
    feature quotas, but this daily safety-net cap was undermining
    them. Now bucketed with Pro on the daily limiter (the monthly
    table still does the actual tier differentiation). Three new
    regression tests cover business + internal + unknown-tier.

  * JD parser leaked section headers and benefits into requirements
    lists. The n8n "AI Product Builder" listing surfaced the literal
    string "REQUIREMENTS / MUST-HAVES" as a Must-Have item, and a
    "Benefits" block adjacent to "Nice to have" pulled vacation /
    PTO / medical / HSA / 401(k) into nice_to_haves. Tightened the
    prompt (explicit "do NOT echo headers" / "do NOT include
    benefits") plus two deterministic post-scrubs:
    _is_section_label_artifact drops heading strings, _looks_like_
    benefit drops compensation vocab. Six new unit tests cover the
    regex + the public _coerce_string_list flags.

35 backend tests pass (5 quota + 12 JD parser + 16 cached_jobs/search
+ 6 new JD LLM scrub). Ruff clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ab, match-score)

Four small UX corrections the in-extension UI test surfaced, all in
the workspace shell:

  * Capitalize plan_tier in the account popover. Backend stores plan
    tiers as lowercase enum-ish strings ("internal", "business",
    "pro", "free", "admin"); the popover rendered them raw. Added a
    formatTier() mapper so users see "Business" / "Internal" / etc.

  * Relabel the "Runs left" indicator for unlimited tiers. When
    max_calls is null (admin / internal) the popover used to show a
    bare "Unlimited" — visually identical to a regular free-tier
    "Unlimited (no quota fetched yet)" failure mode. Now appends the
    tier: "Unlimited (Internal)" / "Unlimited (Admin)". Dev account
    is obviously identifiable; a future "Unlimited" addon for paid
    plans would land in the same shape.

  * Inline CTA on the locked Analysis tab. Previously the disabled
    HTML button was a silent no-op on click — the user reported "no
    inline CTA". Now the chip uses aria-disabled instead of disabled,
    and the click handler routes the user to the missing prereq step
    (Step 01 if no resume, Step 03 if no JD) AND shows a warning
    notice naming the gap. CSS updated: locked chips get cursor: help
    + a faint hover tint so the click affordance is visible.

  * Match-score tile on Job Detail before analysis runs. The metric
    only rendered post-analysis, so a user with a parsed JD but no
    analysis run saw no fit signal at all and didn't know where to
    look. Now always rendered with a "—" placeholder and a hint
    ("Run analysis to compute" or "Re-run analysis (inputs changed)"
    when stale). New .b-jd-metric-hint + data-tone="muted" CSS keeps
    the placeholder visually quieter than the real metric.

tsc + eslint clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated (UTC)
job-application-copilot Ready Ready Preview, Comment May 26, 2026 9:37pm

@LEANDERANTONY LEANDERANTONY merged commit 969959c into main May 26, 2026
4 of 5 checks passed
@LEANDERANTONY LEANDERANTONY deleted the feat/launch-readiness-fixes branch May 26, 2026 21:39
@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 50797507-d94f-42aa-a08a-f53ff4d5127e

📥 Commits

Reviewing files that changed from the base of the PR and between b34a772 and f78bf37.

📒 Files selected for processing (9)
  • backend/services/job_search_service.py
  • frontend/src/app/globals.css
  • frontend/src/components/workspace/JDReview.tsx
  • frontend/src/components/workspace/WorkspaceShell.tsx
  • src/config.py
  • src/services/jd_llm_parser_service.py
  • tests/test_jd_llm_parser_service.py
  • tests/test_job_search_service.py
  • tests/test_quota_service.py

📝 Walkthrough

Walkthrough

This PR introduces four independent feature updates: backend job-search filter preservation, enhanced JD LLM output scrubbing, business-tier quota configuration, and workspace UI improvements for locked steps and stale-metric hints. Job search now threads UI filter selections through the entire cache-backed path; JD parsing adds section-header and benefits filtering; plan tiers are configured consistently; and the workspace shell improves locked-step navigation and metric display.

Changes

Job Search Filter Preservation

Layer / File(s) Summary
Search query filter normalization
backend/services/job_search_service.py
JobSearchQuery now preserves work_modes, employment_types, and normalized sort_by in both search_cached and search paths, and forwards these fields to the cache store RPC call instead of dropping them.
Search filter forwarding test
tests/test_job_search_service.py
Fake cache store is extended to accept the additional filter keyword arguments with proper defaults; new regression test verifies search_cached forwards all filters to the store and applies correct defaults when filters are omitted.

JD Parser Output Scrubbing

Layer / File(s) Summary
Artifact and benefit detection helpers
src/services/jd_llm_parser_service.py
Module adds regex-based detection for LLM-echoed section headers and benefit/perk keywords, extends _coerce_string_list to conditionally filter these categories, tightens the LLM prompt contract to forbid section headers in must_haves and exclude benefits from nice_to_haves, and applies scrubbing in the parse() method.
JD scrubbing behavior test suite
tests/test_jd_llm_parser_service.py
Test file documents scrubbing behaviors and adds focused unit tests for artifact/benefit classification and _coerce_string_list filtering with flag-gated behavior.

Plan Tier Quota Configuration

Layer / File(s) Summary
Business tier daily quota and tier-specific test coverage
src/config.py, tests/test_quota_service.py
get_daily_quota_for_plan now treats business tier as a paid-tier equivalent; new tests verify daily quota routing for business (paid caps), internal/admin (unlimited), and unknown tiers (free fallback).

Workspace UI — Locked Steps and Metric Display

Layer / File(s) Summary
CSS styling for locked steps and metric hints
frontend/src/app/globals.css
Wizard step rail styling distinguishes locked from disabled states via data-locked attribute with reduced opacity and help cursor; hover effects exclude locked steps; new classes support metric hint sublabels and muted-tone metric value rendering.
JDReview match score metric display and hints
frontend/src/components/workspace/JDReview.tsx
Hero metric tile now always shows a "Match score" placeholder with "—" when analysis is stale or absent, conditional hints indicating whether to re-run or run analysis, and muted tone styling; metric structure refactored with optional hint and tone fields, React key moved to outer container, and hint sublabel conditionally rendered.
WorkspaceShell locked step navigation and tier display
frontend/src/components/workspace/WorkspaceShell.tsx
Step rail buttons use aria-disabled and data-locked instead of disabled for locked steps, remaining clickable and computing the first missing prerequisite to navigate to with a contextual warning notice; plan/tier display uses new formatTier helper to normalize tier names and show formatted tier info in unlimited-call labels for internal/admin tiers.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Filters flow through cache, no drop-off here,
Parsed JD lists shed noise with crystal clear,
Locked steps guide gently, metrics hint and fade,
Tiers unified, quotas fairly made!

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/launch-readiness-fixes

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

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f78bf3762a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

"medical, dental", "dental, vision", "health, dental",
"health coverage", "medical coverage", " hsa ", "(hsa)",
"health savings", "401(k)", "401k", " 401 k ", "retirement plan",
"stock options", "equity grant", "rsu", " esop ",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use whole-word match for "rsu" benefit keyword

_looks_like_benefit uses substring checks against padded lowercase text, but the keyword list includes bare "rsu", so any requirement containing that letter sequence (for example, "Pursuing MSc..." or "persuasion skills") is incorrectly classified as a benefit and removed from must_haves/nice_to_haves when drop_benefits=True. This silently drops legitimate qualifications from parsed JD output and can degrade downstream fit matching.

Useful? React with 👍 / 👎.

LEANDERANTONY added a commit that referenced this pull request May 30, 2026
The Next.js app shipped with zero automated tests; CI ran lint + build only, so every piece of client logic (the API error humanizer, auth-session helpers, the quota meter, the tier-gate UI PR #6 changed) was unverified, and the four recent launch commits had no regression net.

Stood up Vitest + React Testing Library + jsdom (the one justified new dependency group for this PR) with a jsdom config (esbuild JSX, @/ alias) and a jest-dom setup whose type augmentation keeps the existing next-build type pass green over .test files. Added a 'npm test' script and a CI Test step between Lint and Build (reusing the same npm ci install). The FE-SEC-1 security headers were extracted to src/lib/securityHeaders.ts (byte-identical) so they can be asserted without loading the Sentry-wrapped next.config.

24 tests across 7 files cover the baseline plus the deferred component tests from earlier commits: humanizeApiError status/leak mapping; api request() 429 -> TierLimitExceededError + POST/credentials wiring (the CRITICAL-2 upgrade-CTA contract); auth-session redirect/cleanup helpers; the security-header set (FE-SEC-1/F4); the useAccessibleDialog Escape + focus-trap behaviour (A11Y-1/A11Y-2); TokenUsageMeter math + over-tone; and the AnalysisRunner premium tier gate (PR #6: a Free tap fires the upgrade CTA, a Pro tap flips the run mode).

Fixes: TEST-1

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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