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
This commit is contained in:
Amruth Pillai
2026-07-05 13:42:14 +02:00
parent 6e7fc68068
commit 893df25aff
59 changed files with 26703 additions and 145 deletions
+44
View File
@@ -0,0 +1,44 @@
import z from "zod";
// Pipeline stages are a fixed enum for v1. If per-user custom stages are ever needed,
// promote this to a table. `rejected` is a terminal stage; `archived` (a boolean on the
// row) hides an application from the board without deleting it.
export const applicationStatusSchema = z.enum(["saved", "applied", "screening", "interview", "offer", "rejected"]);
export type ApplicationStatus = z.infer<typeof applicationStatusSchema>;
// Ordered stage metadata shared by the API (validation) and the web board (columns/colors).
export const STAGES = [
{ value: "saved", label: "Saved", color: "oklch(0.62 0 0)" },
{ value: "applied", label: "Applied", color: "oklch(0.52 0.19 285)" },
{ value: "screening", label: "Screening", color: "oklch(0.45 0.08 195)" },
{ value: "interview", label: "Interview", color: "oklch(0.5 0.1 70)" },
{ value: "offer", label: "Offer", color: "oklch(0.55 0.15 152)" },
{ value: "rejected", label: "Rejected", color: "oklch(0.63 0.12 22)" },
] as const satisfies ReadonlyArray<{ value: ApplicationStatus; label: string; color: string }>;
export const contactSchema = z.object({
name: z.string().trim().min(1),
role: z.string().trim().default(""),
// Free-form label shown as a pill: "Recruiter", "Referral", "Hiring Manager"…
type: z.string().trim().default(""),
});
export type Contact = z.infer<typeof contactSchema>;
// Timeline events: `stage` entries are auto-appended when an application moves; `note`
// entries are added manually from the detail panel.
export const activityEventSchema = z.object({
id: z.string().min(1),
type: z.enum(["stage", "note", "created"]),
text: z.string().trim().min(1),
at: z.coerce.date(),
});
export type ActivityEvent = z.infer<typeof activityEventSchema>;
// Reserved for AI enrichment output (autofill / match-score). Free-form so the shape can
// evolve without a migration. See the AI roadmap in the applications feature.
export const aiMetadataSchema = z.record(z.string(), z.unknown());
export type AiMetadata = z.infer<typeof aiMetadataSchema>;