Files
portfolio/docs/sprint-artifacts/tech-spec-epic-3.md
T
mahdiarghyani 9d05829503 docs(epic-3): add tech spec and story drafts for PDF Export
Epic 3: PDF Export - Planning Phase Complete

Tech Spec:
- Create tech-spec-epic-3.md with 10 acceptance criteria
- Define API contracts, workflows, and NFRs
- Document dependencies (puppeteer, @sparticuz/chromium)
- Add Vercel deployment configuration
- Include traceability mapping and test strategy

Story Drafts:
- 3-1: Create PDF Generation API Route (8 ACs, Puppeteer integration)
- 3-2: Create PDF Download Composable (8 ACs, download logic)
- 3-3: Connect Download Button to PDF Generation (8 ACs, integration)

Sprint Status Updates:
- epic-3: backlog → contexted
- All 3 stories: backlog → drafted

Files Created:
- docs/sprint-artifacts/tech-spec-epic-3.md
- docs/sprint-artifacts/3-1-create-pdf-generation-api-route.md
- docs/sprint-artifacts/3-2-create-pdf-download-composable.md
- docs/sprint-artifacts/3-3-connect-download-button-to-pdf-generation.md

Files Modified:
- docs/sprint-artifacts/sprint-status.yaml

Ready for: Story context generation or implementation
2025-12-01 12:04:37 +03:30

13 KiB
Raw Blame History

Epic Technical Specification: PDF Export

Date: 2025-12-01
Author: mahdi
Epic ID: 3
Status: Draft


Overview

Epic 3 delivers the PDF Export functionality for the Resume Export feature. This epic implements server-side PDF generation using Puppeteer, enabling users to download their resume as a high-quality, ATS-compatible PDF that matches the web preview exactly (WYSIWYG approach).

The implementation leverages the existing /resume page (completed in Epic 2) as the source for PDF generation. Puppeteer navigates to /resume?print=true, captures the page, and returns a PDF buffer. A client-side composable handles the download flow with proper loading states and error handling.

Key Value: One-click PDF download that produces pixel-perfect, ATS-friendly resumes in under 3 seconds.

Objectives and Scope

In Scope:

  • Server-side PDF generation API route (/api/resume/pdf)
  • Puppeteer integration for headless Chrome rendering
  • Client-side composable for download logic (useResumePdf)
  • Download button integration with loading states
  • Error handling with toast notifications
  • Vercel serverless deployment configuration

Out of Scope:

  • Multiple export formats (DOCX, plain text) - future
  • Custom filename input - uses fixed format
  • Resume customization UI - future
  • Persian language PDF - future epic
  • Multiple templates - future

System Architecture Alignment

Architecture Decisions Referenced:

  • ADR-001: Server-side PDF Generation (Puppeteer chosen over client-side libraries)
  • Novel Pattern: WYSIWYG PDF Export (same component for web and PDF)
  • API Pattern: Nuxt Server Route

Components Created:

  • server/api/resume/pdf.get.ts - API endpoint
  • app/composables/useResumePdf.ts - Download logic

Components Modified:

  • app/components/resume/ResumeDownloadButton.vue - Connect to composable

Dependencies on Epic 2:

  • /resume page must exist and render correctly
  • ?print=true query parameter must hide download button
  • Print styles must be properly configured

Detailed Design

Services and Modules

Component Responsibility Inputs Outputs Owner
server/api/resume/pdf.get.ts Generate PDF from resume page GET request PDF binary buffer Story 3.1
app/composables/useResumePdf.ts Handle download flow User click File download Story 3.2
ResumeDownloadButton.vue Trigger download, show loading isPrintMode prop Click event Story 3.3

Data Models and Contracts

No new data models required. Epic 3 uses existing:

  • Resume interface from app/types/resume.ts
  • resumeData from app/data/resume.en.ts
  • useResumeData() composable for getPdfFilename()

PDF Output Contract:

  • Format: A4 (210mm × 297mm)
  • Content-Type: application/pdf
  • Filename: Ali_Arghyani_Resume.pdf (from getPdfFilename())
  • File size: < 500KB target

APIs and Interfaces

GET /api/resume/pdf

// Request
GET /api/resume/pdf

// Response (Success - 200)
Headers:
  Content-Type: application/pdf
  Content-Disposition: attachment; filename="Ali_Arghyani_Resume.pdf"
Body: <PDF binary buffer>

// Response (Error - 500)
Headers:
  Content-Type: application/json
Body: {
  "error": "PDF generation failed",
  "message": "<error details>"
}

useResumePdf Composable Interface:

interface UseResumePdf {
  isGenerating: Ref<boolean>
  downloadPdf: () => Promise<void>
}

function useResumePdf(): UseResumePdf

Workflows and Sequencing

PDF Download Flow:

User clicks "Download PDF" button
         │
         ▼
┌─────────────────────────────────┐
│ ResumeDownloadButton.vue        │
│ - Calls downloadPdf()           │
│ - Shows loading spinner         │
└─────────────────────────────────┘
         │
         ▼
┌─────────────────────────────────┐
│ useResumePdf.ts                 │
│ 1. Set isGenerating = true      │
│ 2. Fetch /api/resume/pdf (blob) │
│ 3. Create object URL            │
│ 4. Create <a> element           │
│ 5. Set download filename        │
│ 6. Trigger click                │
│ 7. Revoke object URL            │
│ 8. Set isGenerating = false     │
└─────────────────────────────────┘
         │
         ▼
┌─────────────────────────────────┐
│ server/api/resume/pdf.get.ts    │
│ 1. Get base URL from request    │
│ 2. Launch Puppeteer browser     │
│ 3. Navigate to /resume?print=true│
│ 4. Wait for networkidle0        │
│ 5. Generate PDF (A4, background)│
│ 6. Close browser                │
│ 7. Return PDF with headers      │
└─────────────────────────────────┘
         │
         ▼
Browser downloads PDF file

Error Flow:

API returns error (500)
         │
         ▼
┌─────────────────────────────────┐
│ useResumePdf.ts                 │
│ 1. Catch error                  │
│ 2. Show toast notification      │
│ 3. Log error to console         │
│ 4. Set isGenerating = false     │
└─────────────────────────────────┘

Non-Functional Requirements

Performance

Metric Target Strategy
PDF generation time < 3 seconds Puppeteer with networkidle0
PDF file size < 500KB Text-only, no images, optimized fonts
Memory usage < 512MB Single browser instance, close after use
API timeout 10 seconds max Vercel function config

Optimization Strategies:

  • Use networkidle0 to ensure fonts are loaded
  • Close browser immediately after PDF generation
  • No external resource loading in PDF
  • Minimal page content (print mode hides extras)

Security

  • Sandboxed execution: Puppeteer runs in sandboxed mode
  • No user input: PDF generation uses server-controlled data only
  • Timeout protection: 10 second max duration prevents resource exhaustion
  • No external resources: PDF doesn't load external URLs
  • HTTPS required: Production deployment uses HTTPS only

Reliability/Availability

  • Graceful degradation: If PDF fails, user sees error toast and can retry
  • Timeout handling: Long-running requests are terminated at 10 seconds
  • Browser cleanup: Browser instance always closed in finally block
  • Retry capability: User can click download again after error

Observability

  • Console logging: Errors logged to console for debugging
  • Toast notifications: User-facing error messages
  • Loading state: Visual feedback during generation
  • Error details: API returns error message for debugging

Dependencies and Integrations

New Dependencies:

Package Version Purpose Environment
puppeteer ^23.x Local PDF generation Development
puppeteer-core ^23.x Serverless PDF generation Production
@sparticuz/chromium ^131.x Chromium for Vercel Production

Installation:

# Development (full Puppeteer with bundled Chromium)
pnpm add puppeteer

# Production (Vercel serverless)
pnpm add puppeteer-core @sparticuz/chromium

Internal Dependencies:

  • app/pages/resume.vue - Source page for PDF
  • app/composables/useResumeData.ts - getPdfFilename() helper
  • @nuxt/ui - useToast() for notifications, UButton for loading state

Configuration:

// vercel.json
{
  "functions": {
    "server/api/resume/pdf.get.ts": {
      "memory": 1024,
      "maxDuration": 10
    }
  }
}

Acceptance Criteria (Authoritative)

AC1: PDF Generation API

Given a GET request to /api/resume/pdf
When the server processes the request
Then it returns a PDF binary with correct headers
And Content-Type is application/pdf
And Content-Disposition includes filename Ali_Arghyani_Resume.pdf

AC2: PDF Content Quality

Given the PDF is generated
When I open the PDF
Then it matches the web preview exactly (WYSIWYG)
And text is selectable and copy-able (ATS-compatible)
And fonts are embedded correctly (Inter)
And colors are preserved (blue headers, white background)

AC3: PDF Dimensions

Given the PDF is generated
When I check the page size
Then it is A4 format (210mm × 297mm)
And margins are consistent with web preview

AC4: Generation Performance

Given I request PDF generation
When the process completes
Then it takes less than 3 seconds
And PDF file size is less than 500KB

AC5: Download Composable

Given I call downloadPdf() from the composable
When the function executes
Then it fetches the PDF as blob
And triggers browser download
And uses correct filename from getPdfFilename()

AC6: Loading State

Given I click the download button
When PDF generation is in progress
Then the button shows loading spinner
And the button is disabled
And loading clears when complete

AC7: Error Handling

Given PDF generation fails
When the error is caught
Then a toast notification appears with error message
And the button returns to normal state
And error is logged to console

AC8: Button Integration

Given the download button is connected to the composable
When I click it
Then downloadPdf() is called
And loading state is bound to button
And button works correctly after error

AC9: Print Mode Compatibility

Given Puppeteer navigates to /resume?print=true
When the page renders
Then download button is hidden
And all content renders correctly
And print styles are applied

AC10: Vercel Deployment

Given the API is deployed to Vercel
When I request PDF generation
Then it works with serverless Chromium
And respects memory and timeout limits

Traceability Mapping

AC Spec Section Components FR Test Idea
AC1 APIs and Interfaces pdf.get.ts FR10, FR12 Request API, verify headers
AC2 Data Models pdf.get.ts FR11, FR12 Open PDF, check text selection
AC3 Data Models pdf.get.ts FR11 Check PDF page dimensions
AC4 Performance pdf.get.ts FR14 Measure generation time
AC5 Workflows useResumePdf.ts FR10 Call composable, verify download
AC6 Workflows ResumeDownloadButton.vue FR10 Click button, check spinner
AC7 Workflows useResumePdf.ts FR10 Simulate error, check toast
AC8 Services ResumeDownloadButton.vue FR10 Click button, verify flow
AC9 Workflows pdf.get.ts FR11 Check print mode rendering
AC10 Dependencies pdf.get.ts FR14 Deploy and test on Vercel

Risks, Assumptions, Open Questions

Risks:

Risk Severity Mitigation
Puppeteer timeout on Vercel Medium Set 10s timeout, optimize page load
Font loading issues in headless Medium Use networkidle0, test thoroughly
Memory limits on serverless Low Use 1024MB, single browser instance
Chromium binary size Low Use @sparticuz/chromium (optimized)

Assumptions:

  • Vercel supports Puppeteer with @sparticuz/chromium
  • Inter font loads correctly in headless Chrome
  • 10 second timeout is sufficient for PDF generation
  • Single PDF generation at a time (no concurrency needed)

Open Questions:

  • Q: Should we cache generated PDFs?
    A: No for MVP - data is static, regeneration is fast enough

  • Q: Should we support custom filenames?
    A: No for MVP - use fixed format from getPdfFilename()

Test Strategy Summary

Unit Tests:

  • useResumePdf composable: mock fetch, verify state changes
  • getPdfFilename() returns correct format

Integration Tests:

  • API route returns PDF with correct headers
  • Full download flow works end-to-end

Manual Tests:

  • Click download button, verify PDF downloads
  • Open PDF, verify content matches preview
  • Test on Vercel deployment
  • Test error handling (disconnect network)
  • Verify ATS compatibility (copy text from PDF)

Performance Tests:

  • Measure generation time (< 3s target)
  • Check PDF file size (< 500KB target)
  • Test under load (optional)

Acceptance Tests:

  • Run through all 10 ACs
  • Verify on multiple browsers
  • Test on mobile (download should work)

Generated by BMAD Epic Tech Context Workflow
Date: 2025-12-01
For: mahdi