Skip to main content

Overview

The Tracker is your central hub for managing all job applications. It provides two distinct views—Board and List—with real-time status management, filtering, and search capabilities.
The Tracker automatically syncs with your job database and displays AI match scores for intelligent prioritization.

Views

Board View (Kanban)

The board view organizes jobs into 5 status columns with drag-and-drop functionality:

Saved

Jobs you’ve bookmarked for later review

Applied

Applications you’ve submitted

Interview

Jobs where you’re in the interview process

Offer

Jobs where you’ve received an offer

Rejected

Applications that didn’t move forward

Board Features

Click and drag any job card between columns to update its status instantly. The system automatically timestamps status changes and logs them in the activity timeline.
// Status update mutation (src/views/app/tracker.tsx:71-73)
const handleUpdateStatus = (jobId: string, status: JobStatus) => {
  updateJobMutation.mutate({ id: jobId, updates: { status } })
}
Each column header has a + button that opens the job creation form with the status pre-selected.
Each job card displays an AI match score bar with color coding:
  • Excellent (80%+): Light gray hsl(0 0% 85%)
  • Good (60-79%): Amber hsl(38 90% 58%)
  • Fair (under 60%): Red hsl(0 65% 58%)
// Score bar component (src/components/tracker/job-card.tsx:19-44)
function ScoreBar({ score }: { score: number }) {
  const color = score >= 80 ? 'hsl(0 0% 85%)' 
              : score >= 60 ? 'hsl(38 90% 58%)' 
              : 'hsl(0 65% 58%)'
  const label = score >= 80 ? 'EXCELLENT' 
              : score >= 60 ? 'GOOD' 
              : 'FAIR'
  // Progress bar animation on mount
}

List View (Table)

The list view displays jobs in a sortable, searchable table format with enhanced density:
1

Search

Type in the search bar to filter by job title or company name. Results update instantly.
// Live search filtering (src/components/tracker/list-view.tsx:237-251)
function applyFiltersAndSearch(jobs: Job[], filters: TrackerFilters, search: string) {
  return jobs.filter(job => {
    if (search) {
      const q = search.toLowerCase()
      if (!job.job_title?.toLowerCase().includes(q) &&
          !job.company_name?.toLowerCase().includes(q)) {
        return false
      }
    }
    // Apply filters...
  })
}
2

Sort Columns

Click any column header to sort by that field. Click again to reverse sort direction.Available Sort Keys:
  • company — Company name (A-Z)
  • role — Job title (A-Z)
  • status — Application status
  • score — AI match score (0-100)
  • date — Creation timestamp
3

Batch Selection

Use checkboxes to select multiple jobs for bulk operations (feature in progress).

Filtering

Click the FILTER button to reveal the filter bar with three categories:
Filter by AI match score ranges:
  • All — Show all jobs
  • 80%+ — Excellent matches only
  • 60-79% — Good matches
  • Under 60% — Fair matches
// Score filtering logic (src/components/tracker/board-view.tsx:140-155)
function applyFilters(jobs: Job[], filters: TrackerFilters): Job[] {
  return jobs.filter(job => {
    if (filters.score === '80plus' && (job.ai_match_score ?? 0) < 80) 
      return false
    if (filters.score === '60to79' && 
        ((job.ai_match_score ?? 0) < 60 || (job.ai_match_score ?? 0) >= 80)) 
      return false
    if (filters.score === 'below60' && (job.ai_match_score ?? 0) >= 60) 
      return false
    return true
  })
}
Active filters are indicated by a count badge on the FILTER button. Click “Clear” to reset all filters.

Keyboard Shortcuts

Keyboard shortcuts are planned but not yet implemented in the current codebase.
Planned shortcuts:
  • Cmd+K — Focus search
  • N — New job
  • B — Switch to board view
  • L — Switch to list view
  • F — Toggle filters

Job Cards

Each job card displays key information at a glance:
// Job card structure (src/components/tracker/job-card.tsx:46-106)
<div className="card-bracket">
  {/* Company logo (first letter) + job title */}
  <div className="flex items-start gap-3">
    <div className="w-8 h-8 bg-background">
      {job.company_name?.[0]}
    </div>
    <div>
      <p className="text-xs font-bold">{job.job_title}</p>
      <p className="text-[10px]">{job.company_name}</p>
    </div>
  </div>

  {/* Location + salary metadata */}
  {job.location && <span><MapPin /> {job.location}</span>}
  {job.salary_range_min && <span><DollarSign /> ${min}k-${max}k</span>}

  {/* AI match score bar */}
  <ScoreBar score={job.ai_match_score} />
</div>

Hover Actions

Hover over a job card to reveal the actions menu (hamburger icon) with options:
  • View Details — Opens the job drawer
  • Edit — Opens the job form
  • Copy Link — Copies the original job URL
  • Delete — Purges the record
Actions are implemented via <JobActions /> component with dropdown menu.

Data Structure

Job Type

// Core Job type (src/lib/types/index.ts:17-54)
export interface Job {
  id: string;
  user_id: string;

  // Core fields
  company_name: string;
  job_title: string;
  job_description: string | null;
  job_url: string | null;
  source: JobSource; // 'brightermonday' | 'fuzu' | 'linkedin' | 'manual'
  status: JobStatus; // 'saved' | 'applied' | 'interview' | 'offer' | 'rejected'

  // AI fields
  ai_match_score: number | null;       // 0-100
  ai_parsed_data: AIParsedData | null;
  ai_reasoning: string | null;

  // Timestamps
  applied_at: string | null;
  interview_at: string | null;
  offer_at: string | null;
  rejected_at: string | null;

  // Metadata
  notes: string | null;
  tags: string[] | null;
  salary_range_min: number | null;
  salary_range_max: number | null;
  location: string | null;
  is_remote: boolean | null;

  // System
  created_at: string;
  updated_at: string;
}

Tracker Filters

// Filter state (src/views/app/tracker.tsx:20-31)
export interface TrackerFilters {
  score: 'all' | '80plus' | '60to79' | 'below60'
  source: 'all' | JobSource
  date: 'all' | '7d' | '30d' | '90d'
}

const DEFAULT_FILTERS: TrackerFilters = {
  score: 'all',
  source: 'all',
  date: 'all',
}

API Endpoints

The Tracker fetches data from:

GET /api/jobs

Returns all jobs for the authenticated user with optional query parameters:
status
JobStatus[]
Filter by status (can provide multiple)
source
JobSource
Filter by job source
min_score
number
Minimum AI match score (0-100)
Search job titles and company names
page
number
default:"1"
Page number for pagination
limit
number
default:"100"
Results per page
Response:
{
  "data": [
    {
      "id": "uuid",
      "company_name": "Acme Corp",
      "job_title": "Senior Engineer",
      "status": "applied",
      "ai_match_score": 87,
      "location": "San Francisco",
      "salary_range_min": 180000,
      "salary_range_max": 250000,
      "created_at": "2026-03-01T10:00:00Z"
    }
  ],
  "total": 42,
  "page": 1,
  "limit": 100,
  "has_more": false
}
// React Query hook usage (src/views/app/tracker.tsx:48-49)
const { data: jobsResponse, isLoading, isError } = useJobs()
const jobs = jobsResponse?.data ?? []

Performance

The Tracker is optimized for large datasets:
For users with 500+ jobs, virtual scrolling will render only visible cards to maintain 60 FPS.
Status changes update the UI immediately while the mutation runs in the background via TanStack Query.

Next Steps

Job Details

Learn about the job drawer and detailed view

Dashboard

Track your application metrics

AI Scoring

Understand how match scores are calculated

Filters API

API reference for job filtering

Build docs developers (and LLMs) love