# Resume Export Feature - Epic Breakdown **Author:** mahdi **Date:** 2025-11-30 **Project Type:** Brownfield Feature Addition **Target:** nuxt-portfolio --- ## Overview This document provides the complete epic and story breakdown for the Resume Export feature, decomposing the 25 functional requirements from the PRD into implementable stories. **Context Documents:** - ✅ PRD: `docs/prd.md` - ✅ Architecture: `docs/architecture.md` --- ## Epic Summary | Epic | Title | Stories | FRs Covered | User Value | |------|-------|---------|-------------|------------| | 1 | Resume Data & Types | 3 | FR1-4 | Data foundation ready | | 2 | Resume Preview Page | 5 | FR5-9, FR15-25 | User can view resume | | 3 | PDF Export | 3 | FR10-14 | User can download PDF | **Total: 3 Epics, 11 Stories** --- ## Functional Requirements Inventory | FR | Description | Epic | Story | |----|-------------|------|-------| | FR1 | System stores resume data in dedicated file | 1 | 1.1 | | FR2 | Resume data structure supports all template sections | 1 | 1.2 | | FR3 | Resume data is independent from portfolio data | 1 | 1.1 | | FR4 | Data structure allows future extension for languages | 1 | 1.2 | | FR5 | Users can view resume at `/resume` route | 2 | 2.1 | | FR6 | Preview page renders resume exactly as PDF | 2 | 2.2-2.4 | | FR7 | Preview page is standalone (no nav) | 2 | 2.1 | | FR8 | Preview page is responsive, optimized for A4 | 2 | 2.2 | | FR9 | Users can see two-column layout | 2 | 2.2 | | FR10 | Users can download resume as PDF | 3 | 3.2 | | FR11 | PDF output matches web preview exactly | 3 | 3.1-3.2 | | FR12 | PDF is ATS-compatible | 2 | 2.2-2.4 | | FR13 | PDF filename follows format | 3 | 3.2 | | FR14 | Download completes in < 3 seconds | 3 | 3.1 | | FR15 | Header displays name and job title | 2 | 2.3 | | FR16 | Contact section shows info with icons | 2 | 2.3 | | FR17 | Skills section displays technical skills | 2 | 2.4 | | FR18 | Education section shows details | 2 | 2.4 | | FR19 | Languages section shows proficiencies | 2 | 2.4 | | FR20 | Profile section displays summary | 2 | 2.3 | | FR21 | Experience section shows jobs | 2 | 2.3 | | FR22 | Resume uses blue/white color scheme | 2 | 2.2 | | FR23 | Typography is professional and ATS-readable | 2 | 2.2 | | FR24 | Layout maintains proper spacing | 2 | 2.2 | | FR25 | Design adapts to content length | 2 | 2.2 | --- ## Epic 1: Resume Data & Types **Goal:** Establish the data foundation for the resume feature with proper TypeScript types and sample data. **User Value:** Resume data is structured, type-safe, and ready for rendering. **FRs Covered:** FR1, FR2, FR3, FR4 --- ### Story 1.1: Create Resume TypeScript Interfaces **As a** developer, **I want** well-defined TypeScript interfaces for resume data, **So that** I have type safety and autocomplete when working with resume content. **Acceptance Criteria:** **Given** the project needs resume data types **When** I create the types file **Then** it includes interfaces for: - `ResumeBasics` (name, label, email, phone, url, location, profiles, summary) - `WorkExperience` (company, position, startDate, endDate, highlights) - `Education` (institution, area, studyType, startDate, endDate) - `Skill` (name, keywords) - `Language` (language, fluency) - `Certification` (name, issuer, date) - `Resume` (combining all above) **And** all date fields use `YYYY-MM` string format **And** optional fields are properly marked with `?` **And** the file is located at `app/types/resume.ts` **Prerequisites:** None **Technical Notes:** - Based on JSON Resume schema (modified) - See Architecture doc "Data Architecture" section - Export all interfaces for use in components --- ### Story 1.2: Create Sample Resume Data File **As a** developer, **I want** a sample resume data file with real content, **So that** I can test the preview and PDF generation. **Acceptance Criteria:** **Given** the TypeScript interfaces exist **When** I create the resume data file **Then** it exports a `resumeData` object of type `Resume` **And** it includes realistic sample data: - Full name and job title - Contact info (email, phone, location) - At least 2 social profiles (LinkedIn, GitHub) - Professional summary (2-3 sentences) - At least 2 work experiences with 3+ highlights each - At least 1 education entry - At least 2 skill categories with 4+ keywords each - At least 2 languages **And** the file is located at `app/data/resume.en.ts` **And** data is independent from existing portfolio data **Prerequisites:** Story 1.1 **Technical Notes:** - Use Ali Arghyani as sample name - Include Iconify icon names for social profiles (i-mdi-linkedin, i-mdi-github) - Dates in YYYY-MM format --- ### Story 1.3: Create Resume Data Composable **As a** developer, **I want** a composable to access resume data, **So that** components can easily consume the data reactively. **Acceptance Criteria:** **Given** the resume data file exists **When** I create the composable **Then** it exports a `useResumeData()` function **And** the function returns: - `resume`: Reactive reference to resume data - `formatDate(date: string, locale?: string)`: Helper to format YYYY-MM dates - `getFullName()`: Returns formatted full name - `getPdfFilename()`: Returns filename like "Ali_Arghyani_Resume.pdf" **And** the file is located at `app/composables/useResumeData.ts` **Prerequisites:** Story 1.2 **Technical Notes:** - Use `computed` for derived values - Date formatting: "Jan 2023" for English - Filename: Replace spaces with underscores --- ## Epic 2: Resume Preview Page **Goal:** Create a pixel-perfect resume preview page that matches the PDF output exactly. **User Value:** User can view their resume in the browser before downloading. **FRs Covered:** FR5-9, FR15-25 --- ### Story 2.1: Create Resume Page Route **As a** user, **I want** to access my resume at `/resume`, **So that** I can view it before downloading. **Acceptance Criteria:** **Given** I navigate to `/resume` **When** the page loads **Then** I see the resume preview **And** the page is standalone (no site header/footer) **And** the page has a white background **And** the page title is "Resume - Ali Arghyani" **And** meta tags are set for SEO (noindex for privacy) **Given** I add `?print=true` query parameter **When** the page loads **Then** the download button is hidden (for PDF generation) **Prerequisites:** None (can use placeholder content initially) **Technical Notes:** - File: `app/pages/resume.vue` - Use `definePageMeta({ layout: false })` for standalone - Check `useRoute().query.print` for print mode --- ### Story 2.2: Create Resume Preview Container Component **As a** developer, **I want** a container component that renders the full resume, **So that** I have a single source of truth for web and PDF. **Acceptance Criteria:** **Given** the ResumePreview component is rendered **When** it displays **Then** it shows a two-column layout: - Left sidebar (35% width): Contact, Skills, Education, Languages - Right main content (65% width): Header, Summary, Experience **And** the container has A4 aspect ratio (210mm × 297mm) **And** page margins are 24px (1.5rem) **And** background is white **And** color scheme is blue (#2563eb) and white **And** typography uses: - Inter font for English text - Proper heading hierarchy (h1 for name, h2 for sections) - Body text: 14px (0.875rem) - ATS-readable font sizes **And** the layout is responsive: - Desktop: Two-column side by side - Mobile: Single column (sidebar on top) **And** print styles are included: - `.no-print` class hides elements in print - Page breaks are controlled - Colors print correctly (`printBackground: true`) **Prerequisites:** Story 1.3 **Technical Notes:** - File: `app/components/resume/ResumePreview.vue` - Use CSS Grid for two-column layout - Tailwind classes for styling - Import and use child components --- ### Story 2.3: Create Resume Header & Main Content Components **As a** user, **I want** to see my name, title, summary, and work experience prominently, **So that** recruiters see the most important information first. **Acceptance Criteria:** **ResumeHeader.vue:** **Given** the header component renders **When** it displays **Then** it shows: - Full name in large bold text (2rem, font-bold) - Job title below name (1.25rem, text-gray-600) - Blue accent line or background element **ResumeSummary.vue:** **Given** the summary component renders **When** it displays **Then** it shows: - "Profile" or "Summary" section header - Professional summary paragraph - Proper line height for readability (1.6) **ResumeExperience.vue:** **Given** the experience component renders **When** it displays **Then** it shows for each job: - Position title (bold) - Company name - Date range (formatted: "Jan 2022 - Present") - Bullet points for highlights (• character) **And** jobs are sorted by date (most recent first) **And** "Present" is shown for current jobs (no endDate) **Prerequisites:** Story 2.2 **Technical Notes:** - Files in `app/components/resume/` - Use `v-for` for work entries - Format dates using composable helper --- ### Story 2.4: Create Resume Sidebar Components **As a** user, **I want** to see my contact info, skills, education, and languages in the sidebar, **So that** this supporting information is easily scannable. **Acceptance Criteria:** **ResumeContact.vue:** **Given** the contact component renders **When** it displays **Then** it shows: - Email with icon (i-mdi-email) - Phone with icon (i-mdi-phone) - Location with icon (i-mdi-map-marker) - Social profiles with icons (LinkedIn, GitHub, etc.) **And** each item is on its own line **And** links are clickable (mailto:, tel:, https://) **ResumeSkills.vue:** **Given** the skills component renders **When** it displays **Then** it shows: - "Skills" section header - Skill categories as subheaders - Keywords as tags or comma-separated list **ResumeEducation.vue:** **Given** the education component renders **When** it displays **Then** it shows for each entry: - Degree type and field (e.g., "B.Sc. Computer Science") - Institution name - Date range **ResumeLanguages.vue:** **Given** the languages component renders **When** it displays **Then** it shows: - "Languages" section header - Each language with fluency level **Prerequisites:** Story 2.2 **Technical Notes:** - Files in `app/components/resume/` - Use Nuxt UI Icon component for icons - Consistent section header styling --- ### Story 2.5: Create Download Button Component **As a** user, **I want** a prominent download button, **So that** I can easily download my resume as PDF. **Acceptance Criteria:** **Given** I'm on the resume page **When** I see the download button **Then** it's a floating action button (FAB) in bottom-right corner **And** it has: - Download icon (i-heroicons-arrow-down-tray) - "Download PDF" text (or just icon on mobile) - Blue background color - Fixed position (doesn't scroll) - Shadow for elevation **And** it has the `.no-print` class (hidden in PDF) **Given** I'm on the page with `?print=true` **When** the page renders **Then** the button is not visible **Prerequisites:** Story 2.1 **Technical Notes:** - File: `app/components/resume/ResumeDownloadButton.vue` - Use Nuxt UI `UButton` component - Click handler will be connected in Epic 3 --- ## Epic 3: PDF Export **Goal:** Enable high-quality PDF generation that matches the web preview exactly. **User Value:** User can download a professional, ATS-compatible PDF resume. **FRs Covered:** FR10-14 --- ### Story 3.1: Create PDF Generation API Route **As a** system, **I want** a server endpoint that generates PDF from the resume page, **So that** users get consistent, high-quality PDF output. **Acceptance Criteria:** **Given** a GET request to `/api/resume/pdf` **When** the server processes the request **Then** it: 1. Launches Puppeteer in headless mode 2. Navigates to `/resume?print=true` 3. Waits for network idle (fonts loaded) 4. Generates PDF with options: - Format: A4 - Print background: true - Margins: default 5. Returns PDF buffer **And** response headers include: - `Content-Type: application/pdf` - `Content-Disposition: attachment; filename="Ali_Arghyani_Resume.pdf"` **And** generation completes in < 3 seconds **And** timeout is set to 10 seconds max **Given** an error occurs during generation **When** the error is caught **Then** it returns: - Status: 500 - JSON: `{ "error": "PDF generation failed", "message": "..." }` **Prerequisites:** Story 2.2 (preview page must exist) **Technical Notes:** - File: `server/api/resume/pdf.get.ts` - Use `puppeteer` package for local dev - Use `puppeteer-core` + `@sparticuz/chromium` for Vercel - See Architecture doc "Deployment Architecture" section --- ### Story 3.2: Create PDF Download Composable **As a** developer, **I want** a composable that handles PDF download logic, **So that** the download button can trigger downloads easily. **Acceptance Criteria:** **Given** I call `downloadPdf()` from the composable **When** the function executes **Then** it: 1. Sets `isGenerating` to true 2. Fetches `/api/resume/pdf` as blob 3. Creates object URL from blob 4. Creates temporary `` element 5. Sets download filename from composable 6. Triggers click to download 7. Revokes object URL 8. Sets `isGenerating` to false **And** the composable returns: - `isGenerating`: Ref - `downloadPdf`: () => Promise **Given** an error occurs during download **When** the error is caught **Then** it: - Shows toast notification: "Error generating PDF" - Sets `isGenerating` to false - Logs error to console **Prerequisites:** Story 3.1 **Technical Notes:** - File: `app/composables/useResumePdf.ts` - Use `$fetch` with `responseType: 'blob'` - Use Nuxt UI `useToast()` for notifications - Get filename from `useResumeData().getPdfFilename()` --- ### Story 3.3: Connect Download Button to PDF Generation **As a** user, **I want** to click the download button and get my PDF, **So that** I can use my resume for job applications. **Acceptance Criteria:** **Given** I click the download button **When** PDF generation starts **Then** the button shows loading state (spinner) **And** the button is disabled **Given** PDF generation succeeds **When** the PDF is ready **Then** the browser downloads the file **And** filename is "Ali_Arghyani_Resume.pdf" **And** button returns to normal state **Given** PDF generation fails **When** the error occurs **Then** a toast notification appears with error message **And** button returns to normal state **Prerequisites:** Story 2.5, Story 3.2 **Technical Notes:** - Update `ResumeDownloadButton.vue` - Use `useResumePdf()` composable - Bind `:loading="isGenerating"` to button - Bind `@click="downloadPdf"` to button --- ## FR Coverage Matrix | FR | Description | Epic | Story | Status | |----|-------------|------|-------|--------| | FR1 | Resume data in dedicated file | 1 | 1.1, 1.2 | ✅ Covered | | FR2 | Data structure supports all sections | 1 | 1.2 | ✅ Covered | | FR3 | Data independent from portfolio | 1 | 1.1, 1.2 | ✅ Covered | | FR4 | Future language extension | 1 | 1.2 | ✅ Covered | | FR5 | View at `/resume` route | 2 | 2.1 | ✅ Covered | | FR6 | Preview matches PDF exactly | 2 | 2.2 | ✅ Covered | | FR7 | Standalone page (no nav) | 2 | 2.1 | ✅ Covered | | FR8 | Responsive, A4 optimized | 2 | 2.2 | ✅ Covered | | FR9 | Two-column layout | 2 | 2.2 | ✅ Covered | | FR10 | Download as PDF | 3 | 3.2, 3.3 | ✅ Covered | | FR11 | PDF matches preview | 3 | 3.1 | ✅ Covered | | FR12 | ATS-compatible | 2 | 2.2-2.4 | ✅ Covered | | FR13 | Filename format | 3 | 3.2 | ✅ Covered | | FR14 | Download < 3 seconds | 3 | 3.1 | ✅ Covered | | FR15 | Header with name/title | 2 | 2.3 | ✅ Covered | | FR16 | Contact with icons | 2 | 2.4 | ✅ Covered | | FR17 | Skills section | 2 | 2.4 | ✅ Covered | | FR18 | Education section | 2 | 2.4 | ✅ Covered | | FR19 | Languages section | 2 | 2.4 | ✅ Covered | | FR20 | Profile/summary section | 2 | 2.3 | ✅ Covered | | FR21 | Experience section | 2 | 2.3 | ✅ Covered | | FR22 | Blue/white color scheme | 2 | 2.2 | ✅ Covered | | FR23 | Professional typography | 2 | 2.2 | ✅ Covered | | FR24 | Proper spacing | 2 | 2.2 | ✅ Covered | | FR25 | Adapts to content length | 2 | 2.2 | ✅ Covered | **Coverage: 25/25 FRs (100%)** --- ## Summary **3 Epics** with **11 Stories** covering all **25 Functional Requirements**. **Implementation Order:** 1. Epic 1: Data foundation (Stories 1.1 → 1.2 → 1.3) 2. Epic 2: Preview page (Stories 2.1 → 2.2 → 2.3 → 2.4 → 2.5) 3. Epic 3: PDF export (Stories 3.1 → 3.2 → 3.3) **Dependencies:** - Epic 2 depends on Epic 1 (needs data) - Epic 3 depends on Epic 2 (needs preview page) **Ready for:** Sprint Planning → Implementation --- _Generated by BMAD Epic Decomposition Workflow_ _Date: 2025-11-30_ _For: mahdi_