04. Frontend Deep Dive¶
Stack and structure¶
| Item | Detail |
|---|---|
| Framework | React 19 + TypeScript |
| Build | Vite 7 |
| Styling | Tailwind CSS 4 |
| UI kit | shadcn / Base UI primitives under frontend/src/components/ui/ |
| Motion | Framer Motion |
| Icons | Lucide |
| Package manager | pnpm (frontend/pnpm-lock.yaml) |
Entrypoints: frontend/src/main.tsx → frontend/src/App.tsx. No client-side router — one screen with conditional views.
API client¶
frontend/src/lib/api.ts:
- Base:
import.meta.env.VITE_API_URLorhttp://localhost:8000, paths under/ocr - Methods:
submitJob,getStatus,downloadResult,downloadResultUrl,listJobs,deleteJob - Submit appends optional
?vlm_mode=
Types mirror backend enums: pending | processing | done | failed.
Main UI flows¶
stateDiagram-v2
[*] --> Landing: App load
Landing --> Uploading: Submit form
Uploading --> Polling: job_id received
Polling --> Results: status=done
Polling --> Failed: status=failed
Results --> Landing: Reset / back
Failed --> Landing: Reset
Landing --> History: Open sheet
History --> Results: View job
History --> Landing: Delete / close
Upload and mode select¶
- Modes hardcoded in
VLM_MODES: layout, vlm, nvidia, sarvam - Card copy claims max 100MB — UI copy only; no client
file.sizereject found beyond display - Accepts PDF/PNG/JPG/TIFF messaging aligned with backend allowlist (webp/bmp also allowed server-side)
Polling¶
setIntervalevery 2000 ms callingapi.getStatus- Progress bar uses
progress / total_blockswhen available - On
done: fetch both Markdown and HTML text into state - On
failed: showerrorfrom status payload
Results¶
- Tabs: HTML preview vs raw Markdown
- HTML rendered via
<iframe srcDoc={...}>with injected dark-theme CSS - ZIP download uses
api.downloadResultUrl(jobId, 'zip')(navigation/link, not fetch-as-text)
History sheet¶
- Lists jobs via
api.listJobs - View rehydrates a past job into the results view
- Delete calls
api.deleteJoband refreshes list
State management¶
Local React useState / useRef only — no Redux, Zustand, or TanStack Query. Appropriate for a single-job-focused dashboard.
Loading / error / empty¶
| State | Behavior |
|---|---|
| Loading | Spinner + disabled controls while submitting/polling |
| Error | Alert with message |
| Empty landing | Upload drop zone + mode select |
| History loading | Separate isJobsLoading flag |
Type safety and build quality¶
- TypeScript project references (
tsconfig.json,tsconfig.app.json) - ESLint config present (
frontend/eslint.config.js) - Confirmed gap: no frontend unit/e2e tests in repo
Frontend interview Q&A¶
Q: Why polling instead of WebSockets?
A: ADR 6 — job frequency/duration make 2s polling simpler and more robust than persistent sockets.
Q: How is the API typed?
A: Hand-written interfaces in api.ts matching FastAPI response models; no OpenAPI codegen.
Q: What is the biggest UX risk?
A: Unsanitized HTML in srcDoc can execute scripts if OCR output contains markup; also the 100MB claim is not enforced.