Add application tracker (#3220)

* feat(applications): job application tracker with AI copilot

Add an Applications module at /dashboard/applications: pipeline board
(dnd-kit), table view with bulk actions, Insights (fit tiles, funnel,
sources, shareable funnel-flow SVG), campaigns, tags, CSV import, and
Add/Edit/Detail slide-overs. Each application links a live Reactive
Resume.

AI "Application Copilot" (applications.ai.*): job-posting autofill,
resume↔job match score (fit ring), resume tailoring, and cover-letter /
follow-up drafting — via the user's configured provider.

Board cards + table rows get context menus (edit / move / archive /
delete). Charts are CSS/SVG (no new chart dep); adds a UI Checkbox.

Also includes local TanStack devtools setup and toolchain bumps.

Claude-Session: https://claude.ai/code/session_01TEeRHnEayw2MFCShFRyL5f

* feat(applications): close follow-up gaps + squash migrations

Finish the deferred/open items on the applications tracker:

- Cover-letter upload re-enabled. Fix the storage blocker by deriving the
  key extension from content type (buildFileKey/EXTENSION_BY_CONTENT_TYPE)
  instead of hardcoding .jpeg, so PDFs serve correctly and non-JPEG image
  avatars keep working under FLAG_DISABLE_IMAGE_PROCESSING. Add
  coverLetterUrl/coverLetterName columns + Documents-section upload/remove.
- Contacts editor in the detail sheet (add/edit/remove, keyed per app).
- Board caps rendered cards per column (COLUMN_PAGE_SIZE=50 + "Show more").
- Extract new Lingui messages across locales.
- Guard coverLetterUrl to http(s)/relative at the API boundary.

Squash the five branch-only application-table migrations (create -> +tags
-> +cover-letter -> drop -> re-add) into a single clean CREATE TABLE via
drizzle-kit generate.

Claude-Session: https://claude.ai/code/session_01TEeRHnEayw2MFCShFRyL5f

* chore: update dependencies

* fix(web): address React Doctor findings — compiler, purity, query, component structure

prefer-module-scope-pure-function: hoist buildSubtitle, getDecimalPlaces,
handleLocaleChange, onLocaleChange, stop, listContent/groupedListContent to
module scope so they aren't rebuilt on every render.

react-compiler-todo (??=): rewrite draft.metadata.styleRules ??= [] to the
non-assignment form to unblock auto-memoization.

set-state-in-effect: derive updatedAtLabel at render time instead of syncing
it through useState + useEffect.

query-destructure-result: destructure useQuery results at call site in
resume-analysis and resume-thumbnail to follow TanStack Query v5 convention.

only-export-components: extract non-component exports to sibling .ts files so
Fast Refresh can preserve component state:
  - getNextWeights → typography/get-next-weights.ts
  - detectJsonImportType + ImportType → dialogs/resume/import.utils.ts
  - getLocaleOptions → features/locale/locale-options.tsx
  - preview helpers + DEFAULT_PDF_PAGE_SIZE → preview.shared.utils.ts
  - resolveHighlightToolbarState + defaultHighlightColor → rich-input.utils.ts
  - computeDelta + getSparklinePoints → statistics.utils.ts

no-multi-comp: split multi-component files into focused companions:
  - ResumePane + ToolbarButton → routes/agent/-components/resume-pane.tsx
  - DesktopBuilderShell → builder/$resumeId/-components/desktop-builder-shell.tsx
  - MobileBuilderShell + helpers → builder/$resumeId/-components/mobile-builder-shell.tsx
  - setBuilderLayout/getBuilderLayout moved to -store/sidebar.ts

fix(tests): add Resume type import to section-builder mocks and cast partial
mock data as unknown as Resume to satisfy stricter type checking; fix
noExplicitAny Biome errors in the same mocks.

* feat(applications): improve performance

* chore: fix knip issues

* perf(builder): halve per-keystroke render cost

Section-form fields called `form.handleSubmit()` on every keystroke, which
re-validated the whole form and toggled submit state — firing the render
cascade twice per character (~6809 renders/keystroke, FPS dropping to 9).

Persist via a form-level `listeners.onChange` instead and drop the per-field
`handleSubmit()` (basics, custom-fields, design). Narrow header/dock resume
subscriptions to metadata slices so they no longer re-render on content edits.

Cuts renders 6809 -> 3403 per keystroke (50%), 0 frame drops. Save, preview,
and design controls verified working; 449/449 web tests pass.

* perf(home): eliminate hero CLS from unreserved video box

The hero <section> is `flex items-center` (shrink-to-fit), so the video
wrapper's width depended on the video's intrinsic size, which only resolves
after the media loads. aspect-ratio couldn't reserve height without a definite
width, so the video grew from ~190px to ~563px after first paint and shoved the
centered hero text down ~373px (CLS ~0.095).

Give the wrapper a definite width (w-full + mx-auto on the CometCard) and set an
explicit aspect ratio + width/height on the video so its box is reserved before
load. CLS 0.095 -> 0; hero stays visually centered at max-w-4xl.

* docs: add application tracker guides

* chore(db): squash application migrations

* fix(email): import React in auth template for server-side rendering compatibility

* chore(release): v5.2.1

* Refactor resume rendering and builder workflows

* fix: address application tracker review findings
This commit is contained in:
Amruth Pillai
2026-07-05 23:44:04 +02:00
committed by GitHub
parent be43b4556b
commit b404dbd42a
198 changed files with 48828 additions and 2246 deletions
+45
View File
@@ -4,6 +4,51 @@ description: "Release notes for Reactive Resume covering new features, resume bu
rss: true
---
<Update label="v5.2.1" description="5th July 2026">
## Highlights
- **Application Tracking is here.** Track every opportunity from saved role to offer with board, table, and insights views; search, filter, sort, tag, archive, and bulk-update applications without keeping a separate spreadsheet. [893df25af](https://github.com/amruthpillai/reactive-resume/commit/893df25af), [e99c1665d](https://github.com/amruthpillai/reactive-resume/commit/e99c1665d), [00a972155](https://github.com/amruthpillai/reactive-resume/commit/00a972155)
- **Application Copilot.** Link a resume and job description to score your fit, create a tailored resume copy, draft a cover letter, or draft a recruiter follow-up from the application detail panel. [893df25af](https://github.com/amruthpillai/reactive-resume/commit/893df25af)
- **Markdown export for resumes.** Export a resume as clean Markdown for docs, websites, AI workflows, or any place where plain text is easier to reuse than a PDF. [20c803e93](https://github.com/amruthpillai/reactive-resume/commit/20c803e93)
- **A cleaner export flow.** Resume and cover-letter downloads now live in a dedicated dialog, with separate PDF, DOCX, JSON, and print actions. [20c803e93](https://github.com/amruthpillai/reactive-resume/commit/20c803e93)
## Application Tracking
- Added the application data model, API routes, database schema, migrations, and service tests for storing applications, contacts, documents, follow-ups, timeline activity, tags, sources, salaries, and stages. [893df25af](https://github.com/amruthpillai/reactive-resume/commit/893df25af), [4ae6d8476](https://github.com/amruthpillai/reactive-resume/commit/4ae6d8476)
- Added the dashboard Applications page with kanban board, table, insights dashboard, detail sheet, create/edit sheet, actions menu, file attachments, CSV import, and archived view. [893df25af](https://github.com/amruthpillai/reactive-resume/commit/893df25af), [00a972155](https://github.com/amruthpillai/reactive-resume/commit/00a972155)
- Added CSV import from uploads or pasted rows, with header recognition, row validation, stage parsing, tag splitting, and importer tests. [893df25af](https://github.com/amruthpillai/reactive-resume/commit/893df25af)
- Added application insights for pipeline counts, response rate, interviews, offers, funnel drop-off, weekly application volume, and source breakdowns. [893df25af](https://github.com/amruthpillai/reactive-resume/commit/893df25af), [00a972155](https://github.com/amruthpillai/reactive-resume/commit/00a972155)
- Added an end-to-end Application Tracker test covering the main application workflow. [af751e4f](https://github.com/amruthpillai/reactive-resume/commit/af751e4f)
## Import & Export
- Added Markdown export support for resumes, powered by new shared resume-section and Markdown helpers so exports preserve the resume's core structure in a portable text format. [20c803e93](https://github.com/amruthpillai/reactive-resume/commit/20c803e93)
- Split resume and cover-letter download behavior so each export path uses the right file and filename. [20c803e93](https://github.com/amruthpillai/reactive-resume/commit/20c803e93)
- Updated PDF download URL handling and export tests for the new download dialog. [20c803e93](https://github.com/amruthpillai/reactive-resume/commit/20c803e93)
## Documentation
- Added guides for tracking job applications and importing applications from CSV, including screenshots and troubleshooting notes. [540796cd6](https://github.com/amruthpillai/reactive-resume/commit/540796cd6)
- Refreshed the resume export and cover-letter guides to match the new download flow. [be43b455](https://github.com/amruthpillai/reactive-resume/commit/be43b455)
## Reliability & Performance
- Improved Application Tracker rendering, tile colors, file-attachment handling, and dashboard responsiveness. [00a972155](https://github.com/amruthpillai/reactive-resume/commit/00a972155)
- Cut builder per-keystroke render cost by tightening the builder header, dock, design panel, and section form updates. [5c75147d](https://github.com/amruthpillai/reactive-resume/commit/5c75147d)
- Improved mobile layouts across the agent pages, builder header, dashboard resume list, and dashboard resume grid. [8416a921](https://github.com/amruthpillai/reactive-resume/commit/8416a921)
- Eliminated layout shift from the homepage hero video by reserving its rendered space. [be1acbc2](https://github.com/amruthpillai/reactive-resume/commit/be1acbc2)
- Fixed server-side rendering for auth email templates by importing React where the template runtime needs it. [af751e4f](https://github.com/amruthpillai/reactive-resume/commit/af751e4f)
## Self-Hosting & Maintenance
- Added the Better Auth two-factor schema migration. [9f9268f3](https://github.com/amruthpillai/reactive-resume/commit/9f9268f3)
- Added design-sync inputs and preview files for Reactive Resume UI components. [a28e3baa](https://github.com/amruthpillai/reactive-resume/commit/a28e3baa), [6e7fc680](https://github.com/amruthpillai/reactive-resume/commit/6e7fc680)
- Synced translation catalogs for the new Application Tracking and export copy. [e99c1665](https://github.com/amruthpillai/reactive-resume/commit/e99c1665), [0d1bfd4e](https://github.com/amruthpillai/reactive-resume/commit/0d1bfd4e)
- Addressed React Doctor findings, cleaned up Knip issues, updated dependencies, and refreshed TypeScript native preview versions. [5a1cc658](https://github.com/amruthpillai/reactive-resume/commit/5a1cc658), [5c1845b1](https://github.com/amruthpillai/reactive-resume/commit/5c1845b1), [91b4c1e6](https://github.com/amruthpillai/reactive-resume/commit/91b4c1e6)
**Full Changelog**: [v5.2.0...v5.2.1](https://github.com/amruthpillai/reactive-resume/compare/v5.2.0...v5.2.1)
</Update>
<Update label="v5.2.0" description="4th July 2026">
## Highlights
+3
View File
@@ -53,6 +53,8 @@
"pages": [
"guides/creating-your-first-resume",
"guides/managing-resumes-from-the-dashboard",
"guides/tracking-job-applications",
"guides/importing-applications-from-csv",
"guides/importing-resumes",
"guides/choosing-a-template",
"guides/selecting-page-format",
@@ -66,6 +68,7 @@
"guides/using-ai-agent",
"guides/using-private-notes",
"guides/exporting-your-resume",
"guides/exporting-resume-to-markdown",
"guides/sharing-your-resume-publicly"
]
},
@@ -0,0 +1,108 @@
---
title: "Exporting a resume to Markdown"
description: "Export your resume as a clean Markdown file for AI tools, personal websites, version control, notes, and quick text edits."
---
Use Markdown when you want the content of your resume in a portable plain-text format. Markdown keeps headings, lists, links, bold text, and italic text, but it does not try to preserve the visual template, page layout, colors, or spacing from the PDF.
Use PDF when you are submitting the finished resume. Use Markdown when you want to reuse, review, transform, or store the resume content.
## Export Markdown
<Steps>
<Step title="Open your resume">
Go to the dashboard and open the resume you want to export.
</Step>
<Step title="Open Download">
Click **Download** in the builder header, or open the **Export** section in the right sidebar and click **Download**.
</Step>
<Step title="Choose Resume or Cover letter">
Select **Resume** to export the resume content. Select **Cover letter** if you want a Markdown copy of the cover letter instead.
</Step>
<Step title="Download Markdown">
In the **Markdown** row, click **Download**. Your browser saves a `.md` file.
</Step>
</Steps>
<Frame caption="Download dialog with the Markdown export option">
<img
src="/images/guides/exporting-resume-to-markdown/screenshot-1.webp"
alt="Download dialog showing PDF, DOCX, Markdown, and JSON export options for a resume"
/>
</Frame>
## Use cases
Markdown export is useful when you need readable resume content outside the PDF.
| Use case | Why Markdown helps |
| --- | --- |
| **AI agents and assistants** | Markdown gives the model structured text with headings and lists, without the layout noise of a PDF. |
| **File search and retrieval systems** | `.md` files are easy to index, search, chunk, and cite in retrieval workflows. |
| **GitHub profile or portfolio repositories** | GitHub renders Markdown files, so you can reuse resume content in a profile README or portfolio repo. |
| **Personal websites and static sites** | Many site generators and CMS workflows accept Markdown as source content. |
| **Notes and knowledge bases** | Apps like Obsidian use Markdown syntax, so your resume can live next to job-search notes and interview prep. |
| **Version control** | Markdown diffs cleanly, making it easier to review what changed between resume versions. |
| **Quick editing** | Open the file in any text editor, make content edits, then copy the text wherever you need it. |
## Example Markdown
A shortened export of the built-in sample resume looks like this:
```md
# David Kowalski
_Game Developer | Unity & Unreal Engine Specialist_
david.kowalski@email.com - +1 (555) 291-4756 - Seattle, WA - [davidkowalski.games](https://davidkowalski.games)
## Summary
**Passionate game developer with 5+ years of professional experience** creating engaging gameplay systems and polished player experiences across multiple platforms. Specialized in Unity and Unreal Engine with strong expertise in C#, C++, and game design principles.
## Experience
### Cascade Studios - Senior Game Developer (March 2022 - Present)
_Seattle, WA_
- Lead gameplay programmer on an unannounced AAA action-adventure title built in Unreal Engine 5 for PC and next-gen consoles
- Architected and implemented core combat system including hit detection, combo mechanics, and enemy AI behavior trees serving 15+ enemy types
- Developed custom editor tools in C++ that reduced level designer iteration time by 40% and improved workflow efficiency across the team
- Optimized rendering pipeline and gameplay systems to maintain 60 FPS performance target on all supported platforms
## Education
### University of Washington - Bachelor of Science, Computer Science (2014 - 2018)
_Seattle, WA - Grade: 3.6 GPA_
Concentration in Game Development. Relevant Coursework: Game Engine Architecture, Computer Graphics, Artificial Intelligence, Physics Simulation, 3D Mathematics, Software Engineering, Data Structures & Algorithms
## Skills
### Unity Engine - Expert
C# - Editor Tools - Performance Profiling
### Unreal Engine - Advanced
C++ - Blueprints - UE5 Features
```
## What Markdown includes
Markdown export includes the visible resume or cover-letter content for the selected tab. It keeps the document structure, section headings, item headings, rich-text paragraphs, lists, links, bold text, and italic text.
Markdown export does not include visual-only choices such as template design, page size, column layout, colors, icon styling, spacing, or PDF page breaks.
<Tip>
If you are sharing the file with an AI assistant, include the job description in the same prompt and ask for changes as Markdown. That makes the result easy to compare before you update the resume in Reactive Resume.
</Tip>
## When not to use Markdown
Do not use Markdown as the final file for most job applications. Applicant portals and recruiters usually expect a PDF or DOCX file. Export Markdown when you need a working copy of the content, then export PDF when you are ready to submit.
+2
View File
@@ -92,6 +92,8 @@ Use Markdown for:
Section headings, lists, links, and rich-text formatting from the builder are preserved as standard Markdown.
For examples and common workflows, see [Exporting a resume to Markdown](/guides/exporting-resume-to-markdown).
## Export as JSON
Choose **JSON** when you want a structured backup of your resume.
@@ -0,0 +1,110 @@
---
title: "Importing applications from CSV"
description: "Import existing job applications into the Application Tracker from a CSV file or pasted spreadsheet rows."
---
Use CSV import when you already track applications in a spreadsheet and want to move them into Reactive Resume.
## Open CSV import
<Steps>
<Step title="Go to Applications">
In the dashboard sidebar, click **Applications**.
</Step>
<Step title="Click Import CSV">
If you have no applications yet, click **Import from CSV** in the empty state. Otherwise, click **Import CSV** in the
page header.
</Step>
</Steps>
<Frame caption="CSV import sheet with a recognized preview">
<img
src="/images/guides/importing-applications-from-csv/screenshot-1.webp"
alt="CSV import sheet showing upload, pasted CSV rows, recognized fields, and one application ready to import"
/>
</Frame>
## Prepare your CSV
The importer uses the first row as headers. Each imported row must include a company and role.
Supported headers include:
| Field | Recognized headers |
| --- | --- |
| **Company** | `Company`, `Employer`, `Organization` |
| **Role** | `Role`, `Title`, `Position`, `Job Title` |
| **Stage** | `Stage`, `Status` |
| **Location** | `Location` |
| **Salary** | `Salary`, `Salary Range`, `Compensation` |
| **Source** | `Source` |
| **Notes** | `Notes`, `Note` |
| **Job posting URL** | `URL`, `Link`, `Job URL`, `Job Posting` |
| **Tags** | `Tags` |
Tags can be separated with commas, semicolons, or vertical bars.
```csv
Company,Role,Stage,Location,Salary,Source,Tags
Stripe,Frontend Engineer,applied,Remote,$180k,LinkedIn,remote;react
```
## Import applications
<Steps>
<Step title="Upload or paste rows">
Upload a `.csv` file, or paste CSV rows directly into the **CSV data** field.
</Step>
<Step title="Review the preview">
Reactive Resume shows how many rows are ready to import, which columns it recognized, and how many rows were skipped.
</Step>
<Step title="Fix skipped rows">
Rows without a company or role are skipped. Add the missing values before importing if you want those rows included.
</Step>
<Step title="Import">
Click **Import**. Imported applications are added to your Application Tracker.
</Step>
</Steps>
## Use valid stages
The **Stage** column is optional. If you include it, use one of these values:
- `saved`
- `applied`
- `screening`
- `interview`
- `offer`
- `rejected`
If a row has an unrecognized stage, the importer ignores that stage value and uses the default application stage.
## Import large files
Reactive Resume imports up to 500 applications at a time. If your CSV has more than 500 valid rows, split it into smaller files and import each file separately.
<Tip>
Import first, then use the table view to select multiple applications and apply tags, move stages, archive rows, or delete rows in bulk.
</Tip>
## Troubleshooting
### Some rows were skipped
Make sure every row has both a company and a role. These fields are required.
### A column was not recognized
Rename the header to one of the recognized names in the table above, then import again.
### Tags did not split correctly
Separate tags with commas, semicolons, or vertical bars, such as `remote;react` or `frontend|senior`.
### A stage did not import
Use lowercase stage values such as `applied` or `interview`. Custom stages are not supported.
+151
View File
@@ -0,0 +1,151 @@
---
title: "Tracking job applications"
description: "Use the Application Tracker to record jobs, link the resume you sent, manage follow-ups, and move applications through your hiring pipeline."
---
The **Application Tracker** helps you keep your job search connected to the resumes you create in Reactive Resume. Each application can store the company, role, stage, job posting, resume, cover letter, contacts, notes, and follow-up details.
## Open the Application Tracker
<Steps>
<Step title="Sign in">
Open Reactive Resume and sign in to your account.
</Step>
<Step title="Go to Applications">
In the dashboard sidebar, click **Applications**.
</Step>
</Steps>
<Frame caption="Application Tracker board view">
<img
src="/images/guides/tracking-job-applications/screenshot-1.webp"
alt="Application Tracker showing search, tag filters, board columns, and application cards grouped by stage"
/>
</Frame>
## Add an application
<Steps>
<Step title="Click Add application">
If this is your first application, use the button in the empty state. Otherwise, use **Add application** in the page header.
</Step>
<Step title="Fill in the role details">
Add the **Company** and **Role / title**. These two fields are required. You can also add the location, salary range,
source, tags, notes, and follow-up details.
</Step>
<Step title="Choose a stage">
Pick the current stage of the opportunity.
</Step>
<Step title="Link or upload the resume you sent">
Choose a Reactive Resume from the **Resume** field when you want to keep a live link to the resume. You can also upload
the exact resume PDF you sent.
</Step>
<Step title="Attach a cover letter">
If you sent a cover letter, attach the PDF in the **Cover letter** field.
</Step>
<Step title="Save">
Click **Add to pipeline**.
</Step>
</Steps>
<Frame caption="Add application form">
<img
src="/images/guides/tracking-job-applications/screenshot-2.webp"
alt="Add application form with job posting auto-fill, company, role, stage, resume, cover letter, tags, and follow-up fields"
/>
</Frame>
<Tip>
Linking a Reactive Resume enables AI match scoring and resume tailoring for that application.
</Tip>
## Use AI to fill a job posting
If you have an AI provider configured, paste a job posting URL at the top of the add form and click **Auto-fill**. Reactive Resume reads the posting and fills in what it can, such as company, role, location, salary, and job description.
If auto-fill fails or the posting is private, paste the job description manually.
For AI setup, see [Using Artificial Intelligence](/guides/using-ai).
## Understand application stages
Applications move through fixed stages:
| Stage | Use it when |
| --- | --- |
| **Saved** | You found a role but have not applied yet. |
| **Applied** | You submitted the application. |
| **Screening** | A recruiter, hiring manager, or automated process is reviewing you. |
| **Interview** | You are in an interview process. |
| **Offer** | You received an offer. |
| **Rejected** | The company declined or the opportunity ended. |
You can move applications by dragging cards on the board, using the action menu, using bulk actions in the table, or opening an application and clicking **Move to**.
## Choose a view
The Application Tracker has three views:
| View | Best for |
| --- | --- |
| **Board** | Moving applications through stages visually. |
| **Table** | Reviewing many applications, selecting rows, and making bulk updates. |
| **Insights** | Understanding your pipeline, response rate, interviews, offers, sources, and application velocity. |
Use search, tag filters, sorting, and the archived toggle to narrow the list.
<Frame caption="Application Tracker insights view">
<img
src="/images/guides/tracking-job-applications/screenshot-4.webp"
alt="Application Tracker insights view showing pipeline metrics, a funnel chart, applications over time, and source counts"
/>
</Frame>
## Update an application
Open an application from the board or table to view its detail panel. From there you can:
- edit the application details;
- move it to the next stage;
- open the original job posting;
- attach or replace resume and cover letter PDFs;
- add contacts;
- add follow-up information;
- add notes to the timeline;
- archive, unarchive, mark rejected, or delete the application.
Stage changes and notes appear in the timeline automatically.
<Frame caption="Application detail panel">
<img
src="/images/guides/tracking-job-applications/screenshot-3.webp"
alt="Application detail panel showing stage progress, linked resume, Application Copilot, contacts, and timeline activity"
/>
</Frame>
## Use Application Copilot
Application Copilot appears in the application detail panel. It can:
- score how well the linked resume matches the job description;
- create a tailored copy of the linked resume;
- draft a cover letter;
- draft a follow-up message.
Match scoring and resume tailoring require both a linked Reactive Resume and a job description. Drafting also works from the application and resume context available in the tracker.
<Warning>
Review AI-generated content before sending it. You are responsible for the final resume, cover letter, and follow-up message.
</Warning>
## Archive or delete applications
Archive applications you want to hide from the active board without deleting their history. Use the **Archived** toggle to view archived applications and unarchive them later.
Delete an application only when you no longer need its record. Deleting an application removes its timeline and cannot be undone.
Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

@@ -0,0 +1,94 @@
# Applications Tracker — Roadmap & Status
**Status legend:** ✅ done · 🟡 in progress · ⬜ not started · 🧊 deferred (intentional, revisit when needed)
A job-application tracker built inside Reactive Resume. Each application points at the live
resume the user sent (`resumeId`), which is why it lives in-product rather than a generic
tracker. Built from the claude.ai/design prototype "Applications Tracker.dc.html".
Owner dirs: `packages/db/src/schema/applications.ts`, `packages/schema/src/applications/`,
`packages/api/src/features/applications/`, `apps/web/src/features/applications/`,
`apps/web/src/routes/dashboard/applications/`.
---
## Phase 0 — Core slice ✅ (shipped)
The working vertical slice: data model, CRUD, board, add/detail panels. Zero new deps.
- ✅ DB: `application` table (user FK cascade, `resumeId` FK set-null, JSONB `contacts`/`activity`, follow-up + AI-reservation columns). Migration `20260705090711_third_hercules`.
- ✅ Schema: `applicationStatusSchema`, `STAGES` (value/label/color), `contactSchema`, `activityEventSchema`, `aiMetadataSchema`.
- ✅ API: oRPC `applications.{list,getById,create,update,addNote,delete}``protectedProcedure`, `userId`-scoped via one `requireOwned`; `update` auto-logs stage-change activity.
- ✅ Web: sidebar nav item; `/dashboard/applications` route (empty state ↔ board).
- ✅ Board: dnd-kit drag across 6 fixed stages, optimistic move + rollback toast.
- ✅ Add slide-over: manual entry + link a live Reactive Resume; native date follow-up.
- ✅ Detail slide-over: key facts, stage stepper, linked resume, contacts, follow-up, activity timeline + add-note, archive/delete.
- ✅ Unit test: activity-logging path (`service.test.ts`). Typecheck / boundaries / biome clean.
**Known small gaps — now closed (✅):**
- ✅ Lingui messages extracted (`pnpm --filter web lingui:extract`) so the new `<Trans>`/`t` strings are translatable.
- ✅ Board caps rendered cards per column (`COLUMN_PAGE_SIZE = 50`) with a "Show N more" button, so a stage with hundreds of applications doesn't mount hundreds of draggable nodes. Server-side paging still unneeded (list payload is small).
- ✅ Contacts are editable from the Detail panel (`ContactsEditor`): add name/role/label, remove, persisted via API `update`'s existing `contacts` field.
---
## Phase 1 — Table view + Insights ✅ (shipped)
-**Table view** — paginated table (25/page), row selection + bulk actions (move stage, add tag, archive, delete). Added a wrapped `Checkbox` in `packages/ui`; hand-rolled table (no `@tanstack/react-table` dep). Board/Table/Insights toggle via URL `view` search param.
-**Insights view** — stat tiles, pipeline funnel (conversion per stage), source bars, and a shareable **funnel-flow SVG** with PNG export (canvas, no chart lib — kept the zero-dep property). Server aggregation `applications.stats` (per-stage/per-source counts); funnel/tiles derived client-side via `computeInsights()` (unit-tested).
- ✅ Tags: `tags text[]` column + filter UI + bulk "add tag" + `applications.tags` distinct list.
- ✅ Archived-toggle so archived rows can be reached and unarchived (closes the archive loop).
## Phase 2 — Campaigns + import + uploads ✅ (shipped)
-**Campaigns** — the `campaign` text field is now first-class: set on create (with a native `<datalist>` autocomplete of existing campaigns), filter on board/table, per-campaign Insights scoping, and an `applications.campaigns` distinct-with-counts endpoint. (Kept it a text field, not a table — grouping/filtering/insights work without the join-table overhead.)
-**CSV import**`Import CSV` opens a slide-over: paste rows or upload a `.csv`, a zero-dep parser (`csv.ts`, unit-tested) maps aliased headers (Company/Role/Stage/Salary/Source/Tags/…), previews recognized columns + ready/skipped counts, then bulk-creates via `applications.import` (max 500, each row gets a `created` activity event). Verified end-to-end in the browser.
-**Cover-letter upload**`coverLetterUrl`/`coverLetterName` columns; the detail panel's Documents section attaches a PDF via the existing `orpc.storage.uploadFile`, shows a download link, and supports removal.
---
## Phase 3 — AI agent integration ✅ (shipped, verified live)
All four `applications.ai.*` procedures are implemented (`packages/api/src/features/applications/ai.ts`), resolving the user's default provider via `aiProvidersService.getDefaultRunnable``getModel``generateText`, with tolerant JSON parsing. Rate-limited via `aiRequestRateLimit`.
-**Job-posting autofill** — paste a URL → server fetches + strips the page → LLM extracts company/role/location/salary/jobDescription → prefills the Add form. (`jobDescription` is now a user-editable field so it persists for match/tailor.)
-**Resume↔job match score** — scores the linked resume vs the job description, persists `matchScore` + gaps in `aiMetadata`. Rendered as a **fit ring** in the redesigned copilot.
-**Tailor resume** — one-shot: duplicates the linked resume, regenerates a job-tailored `summary`, links the copy to the application, logs a timeline event. (No agent/REDIS dependency.)
-**Draft cover letter / follow-up** — generates from application + resume context; shown inline with copy/dismiss.
-**UX**: the AI section was redesigned into an **"Application Copilot"** module — a resume-fit ring (band-colored) as the signature element + a capability menu (icon + title + description) instead of plain buttons.
Verified live against a real OpenAI provider (autofill, match score, tailor resume).
### Extra polish shipped alongside (from review + user requests)
-**Edit an application** — the Add sheet became `ApplicationFormSheet`, edit-capable; reachable from the detail-panel "Edit" button and the context menu.
-**Context menus** — kebab "⋯" on board cards (hover) and table rows: Edit / Move to ▸ / Archive / Delete (`application-actions-menu.tsx`).
- ✅ Review fixes: bulk stage-moves log activity; single-statement note append; "Mark rejected" + "no results" states; CSV BOM strip + 500-row cap + file-input reset.
### ✅ Cover-letter upload (re-enabled)
Un-deferred. The blocker was `storage/service.ts` hardcoding a `.jpeg` key for every upload, so a PDF was read back as an image. Fixed by deriving the key extension from the content type the router already passes (`buildFileKey` + `EXTENSION_BY_CONTENT_TYPE`) — images stay `.jpeg` (unchanged for avatars), PDFs get `.pdf` and the static handler's existing `.pdf` force-download path serves them correctly. Re-added `coverLetterUrl`/`coverLetterName` columns (migration `20260705125353_yielding_star_brand`), DTO editable fields, and a Documents-section upload/download/remove in the detail sheet (PDF-only, best-effort `storage.deleteFile` on remove). Link-to-Reactive-Resume remains the primary document feature; the cover letter sits alongside it.
---
## (historical) Phase 3 plan — reservations in place, no model wired yet
Reservations already shipped: schema fields (`sourceUrl`, `jobDescription`, `matchScore`,
`aiMetadata`) and stubbed `applications.ai.*` procedures that throw `NOT_IMPLEMENTED`
(`packages/api/src/features/applications/ai.ts`). Each stub below just needs its handler
implemented against `@reactive-resume/ai` and the existing agent/thread tables
(`agentThread`, `agentAction` in `packages/db/src/schema/agent.ts`).
-**Job-posting autofill** (`applications.ai.autofill`) — fetch `sourceUrl` (or accept pasted text) → LLM extracts company/role/location/salary/jobDescription → return a prefill object for the Add form; persist raw extraction in `aiMetadata`. UI: enable the disabled "Auto-fill" button in `add-application-sheet.tsx`.
-**Resume ↔ job match score** (`applications.ai.matchScore`) — compare the linked resume's data against `jobDescription` → write `matchScore` (0100) + a gap list into `aiMetadata`. UI: surface a score badge on the card + a gaps section in the Detail panel.
-**Tailor resume for this job** (`applications.ai.tailorResume`) — spawn an agent thread that duplicates the linked resume and edits it for the posting (reuse the agent JSON-patch pipeline); link the new `resumeId` back to the application and log a timeline event.
-**Cover-letter / follow-up drafting** (`applications.ai.draftMessage`) — generate a cover letter or recruiter follow-up from application + resume + contact context. UI: the disabled "AI actions" block in `application-detail-sheet.tsx`.
-**Rate-limiting & provider check** — reuse `aiRequestRateLimit` (already exists) and gate on a configured AI provider (`aiProviders` table) before calling any of the above.
**Suggested order:** autofill (highest user value, self-contained) → match score → draft
message → tailor resume (most complex, depends on the agent pipeline).
---
## Verification (each phase)
- `pnpm --filter @reactive-resume/api test` + `typecheck`, `pnpm --filter web typecheck`, `pnpm exec turbo boundaries`, `pnpm check`.
- DB changes: `dotenvx run -f .env.local -- pnpm db:generate` → review SQL → `pnpm db:migrate`.
- Manual: `dotenvx run -f .env.local -- pnpm dev` (:3000) → exercise the new surface end-to-end.