docs/agent/architecture/frontend-architecture.md
Next.js 15 + React 19 | TypeScript | Tailwind | Swiss International Style
apps/frontend/
├── app/
│ ├── (default)/ # Main app routes
│ │ ├── page.tsx # Landing (/)
│ │ ├── dashboard/ # /dashboard
│ │ ├── builder/ # /builder
│ │ ├── tailor/ # /tailor
│ │ ├── settings/ # /settings
│ │ └── resumes/[id]/ # /resumes/[id]
│ └── print/ # Print routes for PDF
├── components/
│ ├── ui/ # Button, Input, Dialog, etc.
│ ├── builder/ # ResumeBuilder, forms/
│ ├── preview/ # PaginatedPreview, usePagination
│ └── resume/ # Templates (single, two-column)
├── lib/
│ ├── api/ # client.ts, resume.ts, config.ts
│ ├── context/ # status-cache.tsx, language-context.tsx
│ └── constants/ # page-dimensions.ts
└── messages/ # i18n translations
/dashboard)loading | pending | processing | ready | failedmaster_resume_id/builder)/tailor)POST /jobs/upload → POST /resumes/improve/resumes/[new_id]/settings)/print/resumes/[id], /print/cover-letter/[id])Button variants: default (blue), destructive (red), success (green), warning (orange), outline, secondary
Styling: rounded-none, hard shadows, font-mono for labels
const { status, refreshStatus, incrementResumes, decrementResumes } = useStatusCache();
const { contentLanguage, setContentLanguage } = useLanguage();
lib/api/)import { fetchResume, API_BASE } from '@/lib/api';
// client.ts exports
API_URL, API_BASE, apiFetch, apiPost, apiPatch, apiDelete
// resume.ts
uploadJobDescriptions, improveResume, fetchResume, fetchResumeList
updateResume, downloadResumePdf, deleteResume
// config.ts
fetchLlmConfig, updateLlmConfig, testLlmConnection, fetchSystemStatus
| Key | Purpose |
|---|---|
master_resume_id | Master resume UUID |
resume_builder_draft | Auto-saved form data |
resume_builder_settings | Template preferences |
usePagination hook calculates page breaks:
.resume-item boundariesFor PDF generation, globals.css must whitelist print classes:
@media print {
body * { visibility: hidden !important; }
.resume-print, .resume-print * { visibility: visible !important; }
.cover-letter-print, .cover-letter-print * { visibility: visible !important; }
}