- Renamed the H1 from 'Job Agent' to 'Job Tracker' to match the published
repo name.
- Added two Mermaid diagrams up front under 'How it works':
- Architecture diagram: Next.js (RSC + API) ↔ Postgres+pgvector ↔
Claude CLI ↔ local embedder, with HNSW cosine retrieval and the
storage/ folder for uploads + generated PDFs.
- Lifecycle diagram: end-to-end flow from profile setup → application
creation → auto-indexing → generation (CV / cover letter / interview
prep / emails) → review with ATS scoring → approve → pipeline tracking
with unified feed, contacts, and per-company research → outcome →
feedback loop that reindexes positive outcomes for future RAG
retrieval.
Both Gitea and GitHub render Mermaid natively in markdown.
Old README documented the pre-migration SQLite setup and was missing most
of the features added since: contacts CRUD with M:N junction, per-company
research, unified event feed, AI features (interview prep, email gen, ATS),
configurable system prompts, raio-x application detail view.
New README is bilingual at the top (EN+PT short description), then
English-only detailed sections: features by category, tech stack, quick
start with Docker, usage walkthrough, project structure, API reference,
RAG architecture, configurable prompts, data migration paths.
The contact_application junction commit (1d0235a) had already dropped the
deprecated applicationId column from the live DB via migration 0001, but
schema.ts was left with the column declaration (it was put back temporarily
during commit preparation to keep schema.ts consistent with 0000_init.sql,
then never re-removed for the junction commit). This caused Drizzle to
generate INSERTs against a non-existent column when creating contacts.
Schema.ts now matches the live DB; no migration needed (0001 already
dropped the column).
- Pencil icon on each contact card opens inline edit reusing the same field
set as the create form (extracted ContactFields sub-component).
- Company field on the form is a free-text input backed by a <datalist> of
existing companies (fetched from a new GET /api/companies endpoint). Pick
an existing company or type a new name — the contacts API resolves
companyName via findOrCreateCompany, so brand-new companies get created
on the fly without needing to add a phantom application first.
The globals.css had a dark-mode media query that flipped the body to a dark
background, but the rest of the UI was authored light-only — inputs without
an explicit bg-white inherited the dark background and the new text-zinc-900
made typed text invisible. Removed the dark-mode override, declared
color-scheme: light on :root to opt out of UA dark form styling, and added
bg-white + text-zinc-900 placeholder:text-zinc-400 explicitly to every
input/textarea/select.
Also bumped 45 label and section-heading patterns from text-zinc-700 to
text-zinc-800 (slightly stronger), and PageShell h1 / StatCard values to
text-zinc-900 for better hierarchy.
Funnel chart: replaced a brief SVG trapezoid experiment with the bar-chart
form (more legible at small sizes), but kept the new semaphore coloring for
conversion rates (green >=50%, amber 25-49%, red <25%).
The application detail page becomes a "raio-x" view of the role: one
timeline that mixes status changes, interview stages, free-form notes,
and contact interactions; plus per-company research notes and a list of
other applications at the same company so you can see your history with
each employer at a glance.
API:
- POST/PATCH/DELETE /api/applications/[id]/events for note / stage /
contact_interaction events on the unified feed.
- POST/DELETE /api/applications/[id]/contacts to link an existing contact
to an application (optionally with a per-application role).
- GET/POST /api/companies/[id]/research and PATCH/DELETE on the singular
resource for per-company research notes (news, culture, tech_stack,
glassdoor, interview_experience, compensation, general).
- getApplicationById extended to bundle contacts, companyResearch, and
relatedApplications in the same response so the page loads in one fetch.
UI (src/app/applications/[id]/page.tsx):
- FeedPanel replaces the old timeline: per-type icons/colors, inline
composer with type-specific fields (date+outcome for stages, contact
picker for interactions).
- ContactsPanel lists linked contacts and lets you link more via the
composer; pulls /api/contacts and excludes already-linked ones.
- ResearchPanel for per-company research entries (type chip, source URL,
markdown content).
- RelatedAppsPanel showing other applications at the same company.
Contacts (recruiters, interviewers, hiring managers, referrers) live in
their own table with N:M to applications via contact_application, so the
same recruiter can be linked to every role they've put me in front of —
this is the foundation for a networking view across companies.
- /api/contacts: list / create / update / delete contacts (grouped by
company on the UI side).
- Query layer adds linkContactToApplication (ON CONFLICT updates the
junction role for re-links) and unlinkContactFromApplication.
- getContactsByApplication joins through the junction and surfaces both
the contact's general role and the per-application junction role
(e.g. someone could be a "Recruiter" in general but "Hiring manager"
for a specific opening).
- /contacts page lists every contact grouped by company — the starting
point for the networking graph.
- /api/reminders endpoint and reminder query layer: create due/upcoming
follow-ups, mark complete/delete, plus createAutoReminders that fires
the right reminder type when an application transitions to applied /
interview / offer.
- Dashboard (src/app/page.tsx) shows due reminders, stale applications,
drafts awaiting send, response-rate by job-board domain, time-in-stage,
weekly velocity, CV performance — the data already flows from
getDashboardStats.
- Sidebar gains Contacts and Settings entries.
- application-form.tsx extracted so /applications/new and the edit flow
on the detail page share one form component instead of duplicating
field/validation logic.
- POST /api/applications/[id]/interview-prep generates STAR-format prep
material from the candidate's experience.
- POST /api/applications/[id]/generate-email writes follow-up, thank-you,
and withdrawal emails grounded in the application context.
- GET/POST /api/applications/[id]/keywords extracts ATS-relevant terms
from the job description and scores a CV against them (TF-IDF style
scoring, bigram detection, ATS compatibility scoring with formatting
checks).
- generate-cv now feeds the top extracted keywords into the CV prompt so
the output stays ATS-aligned, and returns the keyword analysis plus
ATS score in the response.
- Configurable system prompts (per generation type) stored in the
prompt_config table, editable via /settings; defaults shipped in
src/db/queries/prompt-configs.ts and seeded on first read.
Switch the persistence layer from better-sqlite3 to postgres-js (async),
rewrite the schema in drizzle-orm/pg-core, and add Docker + scripts to
spin up Postgres 16 with pgvector locally on port 5433.
Schema changes beyond the SQLite → PG dialect swap:
- embedding_chunk.embedding becomes vector(384) with an HNSW cosine index,
enabling native similarity search via the `<=>` operator (no more
JSON-serialized arrays + JS-side cosine).
- jsonb for previously-text JSON columns (achievements, content_structured,
metadata).
- application_event generalised into a unified activity feed: adds
event_type, title, contact_id, scheduled_at, completed_at, outcome.
- New tables: contact_application (M:N junction so a recruiter can appear
in multiple applications) and company_research (per-company knowledge log).
Driver swap touches every query call site: all functions in src/db/queries
are now async, and all callers in src/app/api/**/*.ts and src/lib/{rag,claude}
await accordingly. SQLite-specific SQL (julianday, date('now', ...))
translated to Postgres equivalents (extract(epoch ...), current_date,
date_trunc('week', ...)).
Includes scripts/dump-sqlite.mjs and scripts/restore-postgres.mjs to migrate
existing data (run once: dump from old .db, then restore into the running
PG container).
- Implement RAG (Retrieval-Augmented Generation) with local embeddings
(all-MiniLM-L6-v2 via @huggingface/transformers) for semantic search
over documents, experiences, job descriptions, and past generations
- Add embedding_chunk table with Drizzle migration
- Auto-index on document upload, experience CRUD, application creation,
and positive feedback; manual reindex via API/UI
- Integrate RAG retrieval into CV/CL generation context builder with
fallback to legacy concatenation when index is empty
- Redesign dashboard: application funnel, daily activity heatmap,
weekly bar chart, stacked pipeline breakdown, avg response time card
- Add RAG index status panel and rebuild button to Knowledge Base page
- Rewrite README with full project documentation
Complete personal job application management system with:
- Profile management (education, experience, skills, documents)
- Application tracking with status workflow and timeline
- CV and cover letter generation via Claude CLI (claude -p)
- Knowledge base with feedback loop for generation improvement
- Interactive dashboard with stats, charts, and activity feed
- SQLite database with 13 tables via Drizzle ORM (WAL mode)
- File upload with PDF/DOCX/TXT parsing
Stack: Next.js 16, React 19, TypeScript, Tailwind CSS, shadcn/ui,
better-sqlite3, Drizzle ORM, @react-pdf/renderer, Zod validation.