Web UI — architecture & page inventory¶
Status: live · Owner: Harshit Wandhare
The CLI is the engine; the web UI is a thin surface over the same typed core via a local HTTP API. Local-first throughout: the API binds to localhost, the UI runs on your machine, and the optional LLM stays on Ollama or whichever provider you configure. No logic is duplicated between CLI and UI — both call the same profile/repository/tailor/match code.
Next.js 15 + Tailwind (localhost:3000)
│ fetch (lib/api.ts — typed client)
▼
FastAPI (localhost:8000) ──► job_sentinel core
(profile, repo, sources, match, tailor, render)
│
data/ (profile.yaml, jobs.db, documents/)
Backend — FastAPI (api/app.py)¶
All routes bind to localhost; CORS is limited to local origins. Interactive
docs at /docs when the server is running. Full route inventory is in
LLD.md § 8.
Highlights relevant to the web UI:
| Route | Used by |
|---|---|
GET/PUT /api/profile |
Profile editor (view + save) |
POST /api/profile/import-resume |
Profile edit — PDF import |
GET /api/jobs |
Jobs board |
POST /api/match |
AiMatch component (studio + jobs board) |
POST /api/resume/tailor |
Résumé studio live ATS scoring |
POST /api/resume/build |
Studio + jobs board download |
POST /api/resume/cover |
Jobs board cover-letter button |
GET/POST /api/applications |
Applications page CRUD |
GET /api/applications/stats |
Dashboard funnel counts |
GET/DELETE /api/documents |
Résumés page library |
GET /api/documents/{id}/file |
Download PDF |
GET/PUT /api/llm/config, POST /api/llm/test |
Settings page |
GET /api/sources, PUT /api/sources/config |
Settings page |
POST /api/sources/search |
Search page |
POST /api/sources/company |
Search page (company tab) |
GET /api/ops/status, POST /api/ops/* |
Jobs board scraper controls |
POST /api/chat |
Chat page |
Frontend — Next.js 15 / React 19 / Tailwind¶
Lives in web/ as a separate workspace. Uses App Router (no Pages Router).
The typed API client (lib/api.ts) is the single source of truth for the
request/response shapes — it mirrors the FastAPI models exactly; update it
whenever routes change.
Page inventory¶
| Page | File | Purpose |
|---|---|---|
| Landing | app/page.tsx |
Animated intro, self-typing terminal session replay, local-first pitch |
| Dashboard | app/dashboard/page.tsx |
Funnel stats (applications by stage), recent jobs, quick-action cards |
| Job Search | app/search/page.tsx |
Keyword + filter search across enabled sources; SearchResultCard per result; track result → new application |
| Applications | app/applications/page.tsx |
DataTable with full CRUD; stage filter; link to generated résumé |
| Résumé Library | app/resumes/page.tsx |
DataTable of GeneratedDocument records; download PDF; delete |
| Settings | app/settings/page.tsx |
BYO-LLM provider config (chat + embed); job-source enable/disable + API keys; live test buttons |
| Profile (view) | app/profile/page.tsx |
Read-only profile summary with résumé-style layout |
| Profile (edit) | app/profile/edit/page.tsx |
Full section editor (education, experience, projects, skills, certs); résumé-PDF import |
| Jobs Board | app/jobs/page.tsx |
Tracked portal postings; JobsExplorer with per-posting résumé/cover-letter buttons; scraper controls; session status |
| Résumé Studio | app/studio/page.tsx |
Paste a JD → live ATS coverage + AiMatch → tailored PDF; ResumePaper preview |
| Chat | app/chat/page.tsx |
Sentinel assistant (grounded on real data; local LLM for the rest) |
| Login | app/login/page.tsx |
Auth gate shown when AUTH_MODE=demo\|required |
Key components¶
| Component | File | Role |
|---|---|---|
Nav |
components/Nav.tsx |
Top navigation bar |
CommandPalette |
components/CommandPalette.tsx |
⌘K / Ctrl+K overlay for fast navigation |
AiMatch |
components/AiMatch.tsx |
Profile↔job match widget (score ring, verdict, strengths/gaps) |
DataTable |
components/DataTable.tsx |
Reusable sortable/filterable table (used by Applications + Résumés pages) |
SearchResultCard |
components/SearchResultCard.tsx |
Single job-source result card with "Track" action |
JobsExplorer |
components/JobsExplorer.tsx |
Jobs board with scraper controls and per-posting document buttons |
ResumePaper |
components/ResumePaper.tsx |
A4-style résumé preview panel |
ScraperControls |
components/ScraperControls.tsx |
Session status, one-click scrape, watcher toggle |
JobActions |
components/JobActions.tsx |
Per-posting action buttons (apply, tailor, cover) |
JobDocs |
components/JobDocs.tsx |
Generated-document list for a posting |
Typed API client (lib/api.ts)¶
lib/api.ts is the single typed client for all API routes. Keep it in
sync with api/app.py. It exports typed fetch wrappers plus TypeScript
interfaces for every request/response model. The demo shim (lib/demo.ts)
intercepts calls when NEXT_PUBLIC_DEMO=1.
Demo mode (lib/demo.ts)¶
When NEXT_PUBLIC_DEMO=1, every API call in lib/api.ts is intercepted and
returns canned data from lib/demo.ts instead of hitting the local FastAPI
server. This powers the hosted demo on Vercel (job-sentinel.vercel.app) —
every screen is fully alive with realistic but fictional sample data. Nothing
in the demo is real personal data.
To build a demo-mode bundle:
cd web
NEXT_PUBLIC_DEMO=1 npm run build
Standards¶
- Accessibility: WCAG AA baseline via semantic HTML + Tailwind
aria-*props. - Dark mode: Tailwind
dark:variants throughout. - Type-checking:
npm run typecheck(tsc --noEmit). - Tests: vitest (
npm test). - No ESLint config is committed (lint runs via Next.js built-in).
Hosting¶
Runs fully locally now. The same API backs a future managed tier (see
NORTH_STAR.md) without changing the core. The hosted demo
(Vercel) runs in NEXT_PUBLIC_DEMO=1 mode — no backend, no credentials,
purely client-side sample data.