Files
portfolio/docs/epics.md
2025-12-15 14:18:13 +03:30

17 KiB
Raw Blame History

Resume Export Feature - Epic Breakdown

Author: ali 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
  • 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: ali