mirror of
https://github.com/mmahdium/portfolio.git
synced 2026-08-14 20:22:49 +03:30
- Add comprehensive agent workflow definitions for 8 specialized roles (analyst, architect, developer, product manager, scrum master, technical writer, UX designer, QA engineer) - Add 35+ workflow definitions covering analysis, planning, solutioning, and implementation phases - Add BMAD configuration system with agent, task, tool, workflow, and file manifests - Add BMM (Business Model Methodology) documentation including quick-start guides, architecture references, and workflow analysis - Add test architecture knowledge base with 20+ testing patterns and best practices - Add team configuration templates and party mode setup for collaborative development - Establish foundation for enterprise agentic development framework with adaptive scaling capabilities
566 lines
17 KiB
Markdown
566 lines
17 KiB
Markdown
# 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 `<a>` 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<boolean>
|
||
- `downloadPdf`: () => Promise<void>
|
||
|
||
**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_
|